/*
 * DRCtech.c --
 *
 * Technology initialization for the DRC module.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/drc/DRCtech.c,v 1.12 2010/10/20 12:04:12 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "drc/drc.h"
#include "utils/tech.h"
#include "textio/textio.h"
#include "utils/malloc.h"
#include "cif/cif.h"
#include "cif/CIFint.h"
#include "drc/drc.h"

/* C99 compat */
#include "utils/tech.h"
#include "plow/plow.h"

CIFStyle *drcCifStyle = NULL;
bool	 DRCForceReload = FALSE;
HashTable DRCWhyErrorTable;        /* Table of DRC errors */

/*
 * DRC interaction radius being used (not necessarily the same as
 * what's defined in the current DRC style).
 */
global int DRCTechHalo;
global int DRCStepSize;

/* The following variable can be set to zero to turn off
 * any optimizations of design rule lists.
 */

global int DRCRuleOptimization = TRUE;

/* The following variables count how many rules were specified by
 * the technology file and how many edge rules were optimized away.
 */

static int drcRulesSpecified = 0;
static int drcRulesOptimized = 0;

/* Rules with unique names are tagged with a reference number	*/
/* for use in placing errors into the DRC error plane.		*/

static int DRCtag = 0;

/*
 * Forward declarations.
 */
int drcWidth(), drcSpacing(), drcEdge(), drcNoOverlap();
int drcExactOverlap(), drcExtend();
int drcSurround(), drcRectOnly(), drcOverhang();
int drcStepSize(), drcOption(), drcOffGrid();
int drcMaxwidth(), drcArea(), drcRectangle(), drcAngles();
int drcCifSetStyle(), drcCifWidth(), drcCifSpacing();
int drcCifMaxwidth(), drcCifArea();

void DRCTechStyleInit();
void drcLoadStyle();
void DRCTechFinal();
void drcTechFinalStyle();

/*
 * ----------------------------------------------------------------------------
 *
 * Given a TileType bit mask, return the plane mask of planes that are
 * coincident with all types.  This is roughly equivalent to the deprecated
 * DBTechMinSetPlanes(), but does not attempt to produce a reduced set of
 * tile types.  Since the collective mask of all possible planes is usually
 * found by a call to DBTechNoisyNameMask, we don't attempt to OR all the
 * plane masks together, but assume this collective mask is available and
 * passed as an argument.
 *
 * ----------------------------------------------------------------------------
 */

PlaneMask
CoincidentPlanes(typeMask, pmask)
    TileTypeBitMask *typeMask;		/* Mask of types to check coincidence */
    PlaneMask pmask;			/* Mask of all possible planes of types */
{
    PlaneMask planes = pmask;
    TileType i;

    /* AND each plane against the collective mask */
    for (i = TT_SELECTBASE; i < DBNumTypes; i++)
	if (TTMaskHasType(typeMask, i))
	    planes &= DBTypePlaneMaskTbl[i];

    return planes;
}


/*
 * ----------------------------------------------------------------------------
 *
 * Given a plane mask, return the plane number of the lowest plane in the mask.
 *
 * ----------------------------------------------------------------------------
 */

int
LowestMaskBit(PlaneMask pmask)
{
   PlaneMask pset = pmask;
   int plane = 0;

   if (pmask == 0) return DBNumPlanes;

   while ((pset & 0x1) == 0)
   {
	plane++;
	pset >>= 1;
   }
   return plane;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCPrintStyle --
 *
 *	Print the available and/or current extraction styles.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Output.
 *
 * ----------------------------------------------------------------------------
 */

void
DRCPrintStyle(dolist, doforall, docurrent)
    bool dolist, doforall, docurrent;
{
    DRCKeep *style;

    if (docurrent)
    {
	if (DRCCurStyle == NULL)
	    TxError("Error: No style is set\n");
	else
	{
	    if (!dolist) TxPrintf("The current style is \"");
#ifdef MAGIC_WRAPPER
	    if (dolist)
	        Tcl_SetResult(magicinterp, (char *)DRCCurStyle->ds_name, NULL);
	    else
#endif
	    TxPrintf("%s", DRCCurStyle->ds_name);
	    if (!dolist) TxPrintf("\".\n");
	}
    }

    if (doforall)
    {
	if (!dolist) TxPrintf("The DRC styles are: ");

	for (style = DRCStyleList; style; style = style->ds_next)
	{
	    if (dolist)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_AppendElement(magicinterp, style->ds_name);
#else
		if (style != DRCStyleList) TxPrintf(" ");
		TxPrintf("%s", style->ds_name);
#endif
	    }
	    else
	    {
		if (style != DRCStyleList) TxPrintf(", ");
		TxPrintf("%s", style->ds_name);
	    }
	}
	if (!dolist) TxPrintf(".\n");
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCSetStyle --
 *
 *	Set the current DRC style to 'name'.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Output.
 *
 * ----------------------------------------------------------------------------
 */

void
DRCSetStyle(name)
    char *name;
{
    DRCKeep *style, *match;
    int length;

    if (name == NULL) return;

    match = NULL;
    length = strlen(name);
    for (style = DRCStyleList; style; style = style->ds_next)
    {
	if (strncmp(name, style->ds_name, length) == 0)
	{
	    if (match != NULL)
	    {
		TxError("DRC style \"%s\" is ambiguous.\n", name);
		DRCPrintStyle(FALSE, TRUE, TRUE);
		return;
	    }
	    match = style;
	}
    }

    if (match != NULL)
    {
	drcLoadStyle(match->ds_name);
	TxPrintf("DRC style is now \"%s\"\n", name);
	return;
    }

    TxError("\"%s\" is not one of the DRC styles Magic knows.\n", name);
    DRCPrintStyle(FALSE, TRUE, TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcTechFreeStyle --
 *
 *	This procedure frees all memory associated with a DRC style.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory free'd.
 *
 * ----------------------------------------------------------------------------
 */

void
drcTechFreeStyle()
{
    int i, j;
    char *old;
    DRCCookie *dp;

    if (DRCCurStyle != NULL)
    {
	/* Remove all old rules from the DRC rules table */

	for (i = 0; i < TT_MAXTYPES; i++)
	    for (j = 0; j < TT_MAXTYPES; j++)
	    {
		dp = DRCCurStyle->DRCRulesTbl[i][j];
		while (dp != NULL)
		{
		    char *old = (char *)dp;
		    dp = dp->drcc_next;
		    freeMagic(old);
		}
	    }

	/* Clear the Why string list */
	freeMagic(DRCCurStyle->DRCWhyList);

	freeMagic(DRCCurStyle);
	DRCCurStyle = NULL;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcTechNewStyle --
 *
 *	This procedure creates a new DRC style at the end of the list
 *	of styles and initializes it to completely null.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new element is added to the end of DRCStyleList, and DRCCurStyle
 *	is set to point to it.
 *
 * ----------------------------------------------------------------------------
 */

void
drcTechNewStyle()
{
   drcTechFreeStyle();
   DRCTechStyleInit();
}

/*
 * ----------------------------------------------------------------------------
 * drcWhyCreate --
 *
 * Create a hash entry for the DRC "why" string, if it does not already
 * exist.
 *
 * Returns:
 *	A pointer to the drcWhy structure containing the string and tag.
 *
 * Side effects:
 *	Adds to the DRCWhyList if whystring has not been used before.
 *	Calls StrDup() and increments DRCWhySize.  DRCWhyList is allocated
 *	in blocks of 50 at a time and only expands when filled.
 *	Temporary hash table DRCWhyErrorTable is used to determine if a
 *	string entry is unique.  It is cleared after the technology file
 *	has been processed.
 * ----------------------------------------------------------------------------
 */

int
drcWhyCreate(whystring)
    char *whystring;
{
    HashEntry *he;

    he = HashLookOnly(&DRCWhyErrorTable, whystring);
    if (he != NULL)
	return (int)((pointertype)HashGetValue(he));

    /* Grow the list in increments of 50 */
    if ((DRCCurStyle->DRCWhySize % 50) == 0)
    {
	int i;
	char **newList;
	newList = (char **)mallocMagic((DRCCurStyle->DRCWhySize + 51) * sizeof(char *));
	newList[0] = (char *)NULL;
	for (i = 1; i <= DRCCurStyle->DRCWhySize; i++)
	    newList[i] = DRCCurStyle->DRCWhyList[i];
	if (DRCCurStyle->DRCWhySize > 0)
	    freeMagic((char *)DRCCurStyle->DRCWhyList);
	DRCCurStyle->DRCWhyList = newList;
    }
    DRCCurStyle->DRCWhySize++;

    he = HashFind(&DRCWhyErrorTable, whystring);
    HashSetValue(he, (char *)((pointertype)DRCCurStyle->DRCWhySize));

    DRCCurStyle->DRCWhyList[DRCCurStyle->DRCWhySize] = StrDup((char **)NULL, whystring);

    return DRCCurStyle->DRCWhySize;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcFindBucket --
 *
 *	Find the bucket preceding the point where we with to insert a new DRC
 *	cookie.  Don't insert a cookie in the middle of a pair of coupled
 *	(trigger + check) rules.
 *
 * Results:
 *	Returns a pointer to the location where we want to insert a rule
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

DRCCookie *
drcFindBucket(i, j, distance)
    int i, j, distance;
{
    DRCCookie *dp;

    if (DRCCurStyle == NULL) return NULL;

    /* find bucket preceding the new one we wish to insert */

    for (dp = DRCCurStyle->DRCRulesTbl[i][j];
		dp->drcc_next != (DRCCookie *) NULL;
		dp = dp->drcc_next)
    {
	if (dp->drcc_next->drcc_flags & DRC_TRIGGER)
	{
	    if (dp->drcc_next->drcc_next->drcc_dist >= distance)
		break;
	    else
		dp = dp->drcc_next;
	}
	else if (dp->drcc_next->drcc_dist >= distance) break;
    }

    return dp;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcLoadStyle --
 *
 * Re-read the technology file to load the specified technology DRC style
 * into structure DRCCurStyle.  It incurs a complete reading of the tech
 * file on startup and every time the extraction style is changed, but we
 * can assume that this does not happen often.  The first style in the
 * technology file is assumed to be the default, so that re-reading the
 * tech file is not necessary on startup unless the default DRC style is
 * changed by a call do "drc style".
 *
 * ----------------------------------------------------------------------------
 */

void
drcLoadStyle(stylename)
    char *stylename;
{
    SectionID invdrc;

    if (DRCCurStyle->ds_name == stylename) return;

    drcTechNewStyle();		/* Reinitialize and mark as not loaded */
    DRCCurStyle->ds_name = stylename;

    invdrc = TechSectionGetMask("drc", NULL);
    TechLoad(NULL, invdrc);

    DRCTechScale(DBLambda[0], DBLambda[1]);
}

/*
 * ----------------------------------------------------------------------------
 * DRCReloadCurStyle ---
 *
 * This routine is used by CIFLoadStyle whenever the DRC section makes
 * reference to CIF layers.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	DRC rule database is deleted and regenerated.
 *
 * ----------------------------------------------------------------------------
 */

void
DRCReloadCurStyle()
{
    char *stylename;
    DRCKeep * style;

    if (DRCCurStyle == NULL) return;

    for (style = DRCStyleList; style != NULL; style = style->ds_next)
    {
	if (!strcmp(style->ds_name, DRCCurStyle->ds_name))
	{
	    DRCCurStyle->ds_name = NULL;
	    drcLoadStyle(style->ds_name);
	    break;
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 * DRCTechInit --
 *
 * Free and re-initialize the technology-specific variables for the DRC module.
 * This routine is *only* called upon a "tech load" command, when all existing
 * DRC data must be cleaned up and free'd.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Clears out all the DRC tables.
 * ----------------------------------------------------------------------------
 */

void
DRCTechInit()
{
    DRCKeep *style;

    /* Clean up any old info */

    drcTechFreeStyle();

    for (style = DRCStyleList; style != NULL; style = style->ds_next)
    {
	freeMagic(style->ds_name);
	freeMagic(style);
    }
    DRCStyleList = NULL;
}

/*
 * ----------------------------------------------------------------------------
 * DRCTechStyleInit --
 *
 * Initialize the technology-specific variables for the DRC module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Clears out all the DRC tables.
 * ----------------------------------------------------------------------------
 */

void
DRCTechStyleInit()
{
    int i, j, plane;
    DRCCookie *dp;
    PaintResultType result;

    drcRulesOptimized = 0;
    drcRulesSpecified = 0;

    if (DRCCurStyle == NULL)
    {
	DRCCurStyle = (DRCStyle *) mallocMagic(sizeof(DRCStyle));
	DRCCurStyle->ds_name = NULL;
    }

    DRCCurStyle->ds_status = TECH_NOT_LOADED;

    TTMaskZero(&DRCCurStyle->DRCExactOverlapTypes);
    DRCCurStyle->DRCTechHalo = 0;
    DRCCurStyle->DRCScaleFactorN = 1;
    DRCCurStyle->DRCScaleFactorD = 1;
    DRCCurStyle->DRCStepSize = 0;
    DRCCurStyle->DRCFlags = (char)0;
    DRCCurStyle->DRCWhySize = 0;

    HashInit(&DRCWhyErrorTable, 16, HT_STRINGKEYS);

    /* First DRC entry is associated with the statically-allocated  */
    /* drcArrayCookie and has a tag of DRC_ARRAY_OVERLAP_TAG = 1    */
    /* (see DRCarray.c).					    */
    drcWhyCreate("This layer can't abut or partially overlap between array elements");

    /* Second DRC entry is associated with the statically-allocated */
    /* drcOverlapCookie and has a tag of DRC_OVERLAP_TAG = 2	    */
    /* (see DRCbasic.c).					    */
    drcWhyCreate("Can't overlap those layers");

    /* Third DRC entry is associated with the statically-allocated   */
    /* drcSubcellCookie and has a tag of DRC_SUBCELL_OVERLAP_TAG = 3 */
    /* (see DRCsubcell.c).					     */
    drcWhyCreate("This layer can't abut or partially overlap between subcells");

    /* Fourth DRC entry is associated with the statically-allocated  */
    /* drcSubcellCookie and has a tag of DRC_IN_SUBCELL_TAG = 4      */
    /* (see DRCsubcell.c).					     */
    drcWhyCreate("See error definition in the subcell");

    /* Fifth DRC entry is associated with the statically-allocated   */
    /* drcOffGridCookie and has a tag of DRC_OFFGRID_TAG = 5	     */
    /* (see DRCsubcell.c).					     */
    drcWhyCreate("This position does not align with the manufacturing grid");

    DRCTechHalo = 0;

    /* Put a dummy rule at the beginning of the rules table for each entry */

    for (i = 0; i < TT_MAXTYPES; i++)
    {
	for (j = 0; j < TT_MAXTYPES; j++)
	{
	    dp = DRCCurStyle->DRCRulesTbl[i][j];
	    dp = (DRCCookie *) mallocMagic((unsigned) (sizeof (DRCCookie)));
	    dp->drcc_dist = -1;
	    dp->drcc_cdist = -1;
	    dp->drcc_next = (DRCCookie *) NULL;
	    TTMaskZero(&dp->drcc_mask);
	    DRCCurStyle->DRCRulesTbl[i][j] = dp;
	}
    }

    /* Copy the default paint table into the DRC paint table.  The DRC
     * paint table will be modified as we read the drc section.  Also
     * make sure that the error layer is super-persistent (once it
     * appears, it can't be gotten rid of by painting).  Also, make
     * some crossings automatically illegal:  two layers can't cross
     * unless the result of painting one on top of the other is to
     * get one of the layers, and it doesn't matter which is painted
     * on top of which.
     */

    for (plane = 0; plane < DBNumPlanes; plane++)
	for (i = 0; i < DBNumTypes; i++)
	    for (j = 0; j < DBNumTypes; j++)
	    {
		result = DBPaintResultTbl[plane][i][j];
		if ((i == TT_ERROR_S) || (j == TT_ERROR_S))
		    DRCCurStyle->DRCPaintTable[plane][i][j] = TT_ERROR_S;
		else if ((i == TT_SPACE) || (j == TT_SPACE)
			|| !DBTypeOnPlane(j, plane)
			|| !DBPaintOnTypePlanes(i, j))
		    DRCCurStyle->DRCPaintTable[plane][i][j] = result;

		/* Modified for stackable types (Tim, 10/3/03) */
		else if ((i >= DBNumUserLayers) ||
			((result >= DBNumUserLayers)
			&& (DBTechFindStacking(i, j) == result)))
		{
		    DRCCurStyle->DRCPaintTable[plane][i][j] = result;
		}

		else if ((!TTMaskHasType(&DBLayerTypeMaskTbl[i], result)
			&& !TTMaskHasType(&DBLayerTypeMaskTbl[j], result))
			|| ((result != DBPaintResultTbl[plane][j][i])
			&& DBTypeOnPlane(i, plane)
			&& DBPaintOnTypePlanes(j, i)))
		{
		    DRCCurStyle->DRCPaintTable[plane][i][j] = TT_ERROR_S;
		    /* TxError("Error: %s on %s, was %s\n",
				DBTypeLongNameTbl[i], DBTypeLongNameTbl[j],
				DBTypeLongNameTbl[result]); */
		}
		else
		    DRCCurStyle->DRCPaintTable[plane][i][j] = result;
	    }

    drcCifInit();
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCTechLine --
 *
 * Parse a line in the DRC section from the technology file.  Handle DRC
 * styles.  The rules themselves are handled by DRCTechAddRule.
 *
 * Results:
 *      TRUE if line parsed correctly; FALSE if fatal error condition
 *      encountered.
 *
 * Side effects:
 *	Appends information to the DRC tables.
 *
 * ----------------------------------------------------------------------------
 */

bool
DRCTechLine(sectionName, argc, argv)
    char *sectionName;		/* The name of this section */
    int argc;			/* Number of fields on the line */
    char *argv[];		/* Values of the fields */
{
    int j, l;
    DRCKeep *newStyle, *p;
    char *tptr, *cptr;

    if (argc <= 0) return TRUE;
    else if (argc >= 2) l = strlen(argv[1]);

    if (strcmp(argv[0], "style") == 0)
    {
	if (argc != 2)
	{
	    if ((argc != 4) || (strncmp(argv[2], "variant", 7)))
	    {
		wrongNumArgs:
		TechError("Wrong number of arguments in %s statement.\n",
			argv[0]);
		return TRUE;
	    }
	}
	for (newStyle = DRCStyleList; newStyle != NULL;
		newStyle = newStyle->ds_next)
	{
	    /* Here we're only establishing existence;  break on
	     * the first variant found.
	     */
	    if (!strncmp(newStyle->ds_name, argv[1], l))
		break;
	}
	if (newStyle == NULL)
	{
	    if (argc == 2)
	    {
		newStyle = (DRCKeep *)mallocMagic(sizeof(DRCKeep));
		newStyle->ds_next = NULL;
		newStyle->ds_name = StrDup((char **) NULL, argv[1]);

		/* Append to end of style list */
		if (DRCStyleList == NULL)
		    DRCStyleList = newStyle;
		else
		{
		    for (p = DRCStyleList; p->ds_next; p = p->ds_next);
		    p->ds_next = newStyle;
		}
	    }
	    else	/* Handle style variants */
	    {
		DRCKeep *saveStyle = NULL;

		/* 4th argument is a comma-separated list of variants */

		tptr = argv[3];
		while (*tptr != '\0')
		{
		    cptr = strchr(tptr, ',');
		    if (cptr != NULL) *cptr = '\0';
		    newStyle = (DRCKeep *)mallocMagic(sizeof(DRCKeep));
		    newStyle->ds_next = NULL;
		    newStyle->ds_name = (char *)mallocMagic(l
				+ strlen(tptr) + 1);
		    sprintf(newStyle->ds_name, "%s%s", argv[1], tptr);

		    /* Remember the 1st variant as the default */
		    if (saveStyle == NULL) saveStyle = newStyle;

		    /* Append to end of style list */
		    if (DRCStyleList == NULL)
			DRCStyleList = newStyle;
		    else
		    {
			for (p = DRCStyleList; p->ds_next; p = p->ds_next);
			p->ds_next = newStyle;
		    }

		    if (cptr == NULL)
			break;
		    else
			tptr = cptr + 1;
		}
		newStyle = saveStyle;
	    }
	}

	if (DRCCurStyle == NULL)   /* Shouldn't happen, but be safe. . .*/
	{
	    drcTechNewStyle();
	    DRCCurStyle->ds_name = newStyle->ds_name;
	    DRCCurStyle->ds_status = TECH_PENDING;
	}
	else if ((DRCCurStyle->ds_status == TECH_PENDING) ||
			(DRCCurStyle->ds_status == TECH_SUSPENDED))
	    DRCCurStyle->ds_status = TECH_LOADED;
	else if (DRCCurStyle->ds_status == TECH_NOT_LOADED)
	{
	    if (DRCCurStyle->ds_name == NULL) {
		DRCCurStyle->ds_name = newStyle->ds_name;
		DRCCurStyle->ds_status = TECH_PENDING;
	    }
	    else if (argc == 2)
	    {
		if (!strcmp(argv[1], DRCCurStyle->ds_name))
		    DRCCurStyle->ds_status = TECH_PENDING;
	    }
	    else if (argc == 4)
	    {
		/* Verify that the style matches one variant */

		if (!strncmp(DRCCurStyle->ds_name, argv[1], l))
		{
		    tptr = argv[3];
		    while (*tptr != '\0')
		    {
			cptr = strchr(tptr, ',');
			if (cptr != NULL) *cptr = '\0';
			if (!strcmp(DRCCurStyle->ds_name + l, tptr))
			{
			    DRCCurStyle->ds_status = TECH_PENDING;
			    return TRUE;
			}
			if (cptr == NULL)
			    return TRUE;
			else
			    tptr = cptr + 1;
		    }
		}
	    }
	}
	return (TRUE);
    }

    if (DRCCurStyle == NULL)
	return FALSE;

    /* For backwards compatibility, if we have encountered a line that	*/
    /* is not "style" prior to setting a style, then we create a style	*/
    /* called "default".						*/

    if (DRCStyleList == NULL)
    {
	char bufargv[2][10];
	char *locargv[2];

	strcpy(bufargv[0], "style");
	strcpy(bufargv[1], "default");

	locargv[0] = bufargv[0];
	locargv[1] = bufargv[1];

	if (DRCTechLine(sectionName, 2, locargv) == FALSE)
	    return FALSE;
    }
    else if (DRCStyleList->ds_next == NULL)
    {
	/* On reload, if there is only one style, use it.  This is	*/
	/* necessary for the DRC-CIF extensions, since even though	*/
	/* the one-and-only DRC section may have been loaded, the DRC-	*/
	/* CIF parts of it become enabled or disabled depending on what	*/
	/* CIFCurStyle is active.					*/

	DRCCurStyle->ds_status = TECH_PENDING;
    }

    /* Only continue past this point if we are loading the DRC style */

    if ((DRCCurStyle->ds_status != TECH_PENDING) &&
		(DRCCurStyle->ds_status != TECH_SUSPENDED))
	return TRUE;

    /* Process "scalefactor" line next (if any) */

    if (strcmp(argv[0], "scalefactor") == 0)
    {
	int scaleN, scaleD;

	if (argc != 2 && argc != 3) goto wrongNumArgs;

	scaleN = atof(argv[1]);

	if (argc == 3)
	    scaleD = atof(argv[2]);
	else
	    scaleD = 1;

	if (scaleN <= 0 || scaleD <= 0)
	{
	    TechError("Scale factor must be greater than 0.\n");
	    TechError("Setting scale factor to default value 1.\n");
	    DRCCurStyle->DRCScaleFactorN = 1;
	    DRCCurStyle->DRCScaleFactorD = 1;
	    return TRUE;
	}
	DRCCurStyle->DRCScaleFactorN = scaleN;
	DRCCurStyle->DRCScaleFactorD = scaleD;
	return TRUE;
    }

    /* Process "variant" lines next. */

    if (strncmp(argv[0], "variant", 7) == 0)
    {
	/* If our style variant is not one of the ones declared */
	/* on the line, then we ignore all input until we	*/
	/* either reach the end of the style, the end of the 	*/
	/* section, or another "variant" line.			*/

	if (argc != 2) goto wrongNumArgs;
	tptr = argv[1];
	while (*tptr != '\0')
	{
	    cptr = strchr(tptr, ',');
	    if (cptr != NULL)
	    {
		*cptr = '\0';
		for (j = 1; isspace(*(cptr - j)); j++)
		    *(cptr - j) = '\0';
	    }

	    if (*tptr == '*')
	    {
		DRCCurStyle->ds_status = TECH_PENDING;
		return TRUE;
	    }
	    else
	    {
		l = strlen(DRCCurStyle->ds_name) - strlen(tptr);
		if (!strcmp(tptr, DRCCurStyle->ds_name + l))
		{
		    DRCCurStyle->ds_status = TECH_PENDING;
		    return TRUE;
		}
	    }

	    if (cptr == NULL)
		break;
	    else
		tptr = cptr + 1;
	}
	DRCCurStyle->ds_status = TECH_SUSPENDED;
    }

    /* Anything below this line is not parsed if we're in TECH_SUSPENDED mode */
    if (DRCCurStyle->ds_status != TECH_PENDING) return TRUE;

    return DRCTechAddRule(sectionName, argc, argv);
}

void
drcCifAssign(cookie, dist, next, mask, corner, tag, cdist, flags, planeto, planefrom)
    DRCCookie *cookie, *next;
    int dist, cdist;
    TileTypeBitMask *mask, *corner;
    int tag;
    unsigned short flags;
    int planeto, planefrom;
{
    (cookie)->drcc_dist = dist;
    (cookie)->drcc_next = next;
    (cookie)->drcc_mask = *mask;
    (cookie)->drcc_corner = *corner;
    (cookie)->drcc_tag = tag;
    (cookie)->drcc_cdist = cdist;
    (cookie)->drcc_flags = flags;
    (cookie)->drcc_edgeplane = planefrom;
    (cookie)->drcc_plane = planeto;
    (cookie)->drcc_mod = 0;
    (cookie)->drcc_cmod = 0;
}

// This is like drcCifAssign, but checks for bad plane numbers in planeto and
// planefrom

void
drcAssign(cookie, dist, next, mask, corner, why, cdist, flags, planeto, planefrom)
    DRCCookie *cookie, *next;
    int dist, cdist;
    TileTypeBitMask *mask, *corner;
    int why;
    unsigned short flags;
    int planeto, planefrom;
{
    /* Diagnostic */
    if (planeto >= DBNumPlanes)
	TechError("Bad plane in DRC assignment.\n");
    if (planefrom >= DBNumPlanes)
	TechError("Bad edge plane in DRC assignment.\n");

    drcCifAssign(cookie, dist, next, mask, corner, why, cdist, flags, planeto,
	    planefrom);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCTechAddRule --
 *
 * Add a new entry to the DRC table.
 *
 * Results:
 *	Always returns TRUE so that tech file read-in doesn't abort.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * Organization:
 *	We select a procedure based on the first keyword (argv[0])
 *	and call it to do the work of implementing the rule.  Each
 *	such procedure is of the following form:
 *
 *	int
 *	proc(argc, argv)
 *	    int argc;
 *	    char *argv[];
 *	{
 *	}
 *
 * 	It returns the distance associated with the design rule,
 *	or -1 in the event of a fatal error that should cause
 *	DRCTechAddRule() to return FALSE (currently, none of them
 *	do, so we always return TRUE).  If there is no distance
 *	associated with the design rule, 0 is returned.
 *
 * ----------------------------------------------------------------------------
 */


	/* ARGSUSED */
bool
DRCTechAddRule(sectionName, argc, argv)
    char *sectionName;		/* Unused */
    int argc;
    char *argv[];
{
    int which, distance, mdist;
    const char *fmt;
    static const struct
    {
	const char *rk_keyword;	/* Initial keyword */
	int	 rk_minargs;	/* Min # arguments */
	int	 rk_maxargs;	/* Max # arguments */
	int    (*rk_proc)();	/* Procedure implementing this keyword */
	const char *rk_err;	/* Error message */
    } ruleKeys[] = {
	{"angles",	 4,	4,	drcAngles,
    "layers 45|90 why"},
	{"edge",	 8,	10,	drcEdge,
    "layers1 layers2 distance okTypes cornerTypes cornerDistance [option] why [plane]"},
	{"edge4way",	 8,	10,	drcEdge,
    "layers1 layers2 distance okTypes cornerTypes cornerDistance [option] why [plane]"},
	{"exact_overlap", 2,	2,	drcExactOverlap,
    "layers"},
	{"extend",	 5,	6,	drcExtend,
    "layers1 layers2 distance [option] why"},
	{"no_overlap",	 3,	3,	drcNoOverlap,
    "layers1 layers2"},
	{"option",	 2,	2,	drcOption,
    "option_name option_value"},
	{"overhang",	 5,	5,	drcOverhang,
    "layers1 layers2 distance why"},
	{"rect_only",	 3,	3,	drcRectOnly,
    "layers why"},
	{"spacing",	 6,	7,	drcSpacing,
    "layers1 layers2 separation [layers3] adjacency why"},
	{"stepsize",	 2,	2,	drcStepSize,
    "step_size"},
	{"surround",	 6,	7,	drcSurround,
    "layers1 layers2 distance presence why"},
	{"width",	 4,	5,	drcWidth,
    "layers width why"},
	{"widespacing",	 7,	8,	drcSpacing,
    "layers1 width layers2 separation adjacency why"},
        {"area",	 5,	5,	drcArea,
    "layers area horizon why"},
        {"off_grid",	 4,	4,	drcOffGrid,
    "layers pitch why"},
        {"maxwidth",	 4,	6,	drcMaxwidth,
    "layers maxwidth bends why"},
	{"cifstyle",	 2,	2,	drcCifSetStyle,
    "cif_style"},
	{"cifwidth",	 4,	4,	drcCifWidth,
    "layers width why"},
	{"cifspacing",	 6,	6,	drcCifSpacing,
    "layers1 layers2 separation adjacency why"},
	{"cifarea",	 5,	5,	drcCifArea,
    "layers area horizon why"},
	{"cifmaxwidth",	 5,	5,	drcCifMaxwidth,
    "layers maxwidth bends why"},
	{"rectangle",	5,	5,	drcRectangle,
    "layers maxwidth [even|odd|any] why"},
	{0}
    }, *rp;

    drcRulesSpecified += 1;

    which = LookupStruct(argv[0], (const LookupTable *) ruleKeys, sizeof ruleKeys[0]);
    if (which < 0)
    {
	TechError("Bad DRC rule type \"%s\"\n", argv[0]);
	TxError("Valid rule types are:\n");
	for (fmt = "%s", rp = ruleKeys; rp->rk_keyword; rp++, fmt = ", %s")
	    TxError(fmt, rp->rk_keyword);
	TxError(".\n");
	return (TRUE);
    }
    rp = &ruleKeys[which];
    if (argc < rp->rk_minargs || argc > rp->rk_maxargs)
    {
	TechError("Rule type \"%s\" usage: %s %s\n",
		rp->rk_keyword, rp->rk_keyword, rp->rk_err);
	return (TRUE);
    }

    distance = (*rp->rk_proc)(argc, argv);
    if (distance < 0)
	return (FALSE);

    /* Update the halo to be the maximum distance (in magic units) of	*/
    /* any design rule							*/

    mdist = distance;

    if (mdist > DRCTechHalo)
	DRCTechHalo = mdist;

    return (TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcExtend --
 *
 * Process an extension rule.
 * This is of the form:
 *
 *	extend layers1 layers2 distance [exact_width | exclusive] why
 *
 * indicating that if "layers1" extends from "layers2", then it must
 * have a width of at least "distance".  This is very much like the
 * "overhang" rule, except that the extension is optional.  Example:
 *
 *	extend nfet *poly 3 "n-transistor width must be at least 3"
 *
 * "extend" implements the following general-purpose edge rule:
 *
 *	edge4way layers2 layers1 distance layers1 0 0 why
 *
 * Option "exact_width" implements an additional rule that checks for
 * maximum extension at distance.  This is intended for use with, for
 * example, a fixed gate length for a specific type of device.
 *
 * Option "exclusive" is for use for, e.g., transistor length when the
 * length rule is larger than the minimum width rule.  However, without
 * the option, "extend" sets OKtypes to the union of layers1 and layers2;
 * that covers, e.g., extension of diffusion from a gate where an
 * additional gate within the distance does not violate the rule.  With
 * "exclusive", layers2 may not appear within "distance" of the edge.
 *
 * Results:
 *	Returns distance.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcExtend(argc, argv)
    int argc;
    char *argv[];
{
    char *layers1 = argv[1];
    char *layers2 = argv[2];
    int distance = atoi(argv[3]);
    int why;
    TileTypeBitMask set1, setC;
    DRCCookie *dp, *dpnew, *dptrig;
    TileType i, j;
    int plane, plane2;
    TileTypeBitMask set2, setZ, setN, setM;
    PlaneMask pMask1, pMask2, pset, ptest;
    bool exact = FALSE;
    bool exclusive = FALSE;

    if (!strncmp(argv[4], "exact_", 6))
    {
	exact = TRUE;
	why = drcWhyCreate(argv[5]);
    }
    else if (!strncmp(argv[4], "exclu", 5))
    {
	exclusive = TRUE;
	why = drcWhyCreate(argv[5]);
    }
    else
	why = drcWhyCreate(argv[4]);

    ptest = DBTechNoisyNameMask(layers1, &set1);
    pMask1 = CoincidentPlanes(&set1, ptest);

    if (pMask1 == 0)
    {
	TechError("All layers in first set for \"extend\" must be on "
			"the same plane\n");
	return (0);
    }

    ptest = DBTechNoisyNameMask(layers2, &set2);
    pMask2 = CoincidentPlanes(&set2, ptest);

    if (pMask2 == 0)
    {
	TechError("All layers in second set for \"extend\" must be on "
			"the same plane\n");
	return (0);
    }

    /* setM is the union of set1 and set2 unless exclusive == TRUE */
    TTMaskZero(&setM);
    if (exclusive)
	TTMaskSetMask(&setM, &set1);
    else
	TTMaskSetMask3(&setM, &set1, &set2);

    /* setN is the inverse of set1, and setC is the inverse of set2 */
    TTMaskCom2(&setN, &set1);
    TTMaskCom2(&setC, &set2);

    /* Zero mask */
    TTMaskZero(&setZ);

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    if ((pset = (DBTypesOnSamePlane(i, j) & pMask2)))
	    {
		/* Edge depends on whether or not the extension is 	*/
		/* on the same plane as the layer from which it is	*/
		/* measured.						*/

		if ((pset & pMask1) != 0)
		{
		    if (TTMaskHasType(&set2, i) && TTMaskHasType(&set1, j))
		    {
			plane = LowestMaskBit(pset & pMask1);

			/* find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &setM, &setZ, why,
				    0, DRC_FORWARD, plane, plane);

			dp->drcc_next = dpnew;

			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &setM, &setZ, why,
				    0, DRC_REVERSE, plane, plane);

			dp->drcc_next = dpnew;

			if (exact)
			{
			    /* find bucket preceding the new one we wish to insert */
			    dp = drcFindBucket(i, j, distance);
			    dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			    drcAssign(dpnew, distance, dp->drcc_next, &setN, &setZ, why,
				    0, DRC_FORWARD | DRC_OUTSIDE, plane, plane);

			    dp->drcc_next = dpnew;

			    dp = drcFindBucket(j, i, distance);
			    dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			    drcAssign(dpnew, distance, dp->drcc_next, &setN, &setZ, why,
				    0, DRC_REVERSE | DRC_OUTSIDE, plane, plane);

			    dp->drcc_next = dpnew;
			}
		    }
		}
		else	/* Multi-plane extend rule */
		{
		    if (TTMaskHasType(&set2, i) && TTMaskHasType(&setC, j))
		    {
			plane = LowestMaskBit(pset);
			plane2 = LowestMaskBit(pMask1);

			/* find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &setM, &setZ, why,
				    0, DRC_FORWARD, plane2, plane);
			dptrig = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dptrig, 1, dpnew, &setN, &setZ, why,
				    0, DRC_FORWARD | DRC_TRIGGER, plane2, plane);
			dp->drcc_next = dptrig;

			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &setM, &setZ, why,
				    0, DRC_REVERSE, plane2, plane);
			dptrig = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dptrig, 1, dpnew, &setN, &setZ, why,
				    0, DRC_REVERSE | DRC_TRIGGER, plane2, plane);
			dp->drcc_next = dptrig;
		    }
		}
	    }
	}
    }
    return (distance);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcWidth --
 *
 * Process a width rule.
 * This is of the form:
 *
 *	width layers distance [angles] why
 *
 * e.g,
 *
 *	width poly,pmc 2 "poly width must be at least 2"
 *
 * The option "angles" is computed only on non-manhattan edges and is used
 * in cases were, for example, an angled transistor has a minimum width
 * larger than that of a straight gate.
 *
 * Results:
 *	Returns distance.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcWidth(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    int distance = atoi(argv[2]);
    int why;
    TileTypeBitMask set, setC;
    PlaneMask pmask, pset, ptest;
    DRCCookie *dp, *dpnew;
    TileType i, j;
    int plane, flags = 0;

    if (!strncmp(argv[3], "angle", 5))
    {
	flags = DRC_SPLITTILE;
	why = drcWhyCreate(argv[4]);
    }
    else
	why = drcWhyCreate(argv[3]);

    ptest = DBTechNoisyNameMask(layers, &set);
    pmask = CoincidentPlanes(&set, ptest);
    TTMaskCom2(&setC, &set);

    if (pmask == 0)
    {
	TechError("All layers for \"width\" must be on same plane\n");
	return (0);
    }

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    /*
	     * Must have types in 'set' for at least 'distance'
	     * to the right of any edge between a type in '~set'
	     * and a type in 'set'.
	     */

	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
	    {
		if (TTMaskHasType(&setC, i) && TTMaskHasType(&set, j))
		{
		    plane = LowestMaskBit(pset);

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, distance, dp->drcc_next, &set, &set,
				why, distance, DRC_FORWARD | flags, plane, plane);
		    dp->drcc_next = dpnew;
		}

		/* Width checks are symmetric and so need to be checked	*/
		/* in only one direction.  However, split tiles are	*/
		/* not symmetric, so apply the reverse case with a flag	*/
		/* that will allow the rule to be applied only in the	*/
		/* case of a split tile.				*/

		if (TTMaskHasType(&set, i) && TTMaskHasType(&setC, j))
		{
		    plane = LowestMaskBit(pset);

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, distance, dp->drcc_next, &set, &set,
				why, distance, DRC_REVERSE | DRC_SPLITTILE,
				plane, plane);
		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return (distance);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcArea --
 *
 * Process an area rule.
 * This is of the form:
 *
 *	area layers area horizon why
 *
 * e.g,
 *
 *	area pmc 4 2 "poly contact area must be at least 4"
 *
 * "area" is the total area in lambda^2.
 *
 * "horizon" is the halo distance for the check.  Normally, this would
 * be the area (in lambda^2) divided by the minimum width rule (in
 * lambda).  Anything larger would not be a violation as long as the
 * minimum width rule is satisfied.
 *
 * Results:
 *	Returns distance.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcArea(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    int distance = atoi(argv[2]);
    int	horizon = atoi(argv[3]);
    int why = drcWhyCreate(argv[4]);
    TileTypeBitMask set, setC;
    DRCCookie *dp, *dpnew;
    TileType i, j;
    PlaneMask pmask, ptest, pset;
    int plane;

    ptest = DBTechNoisyNameMask(layers, &set);
    pmask = CoincidentPlanes(&set, ptest);
    TTMaskCom2(&setC, &set);

    if (pmask == 0)
    {
	TechError("All layers for \"area\" must be on same plane\n");
	return (0);
    }

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    /*
	     * Must have types in 'set' for at least 'distance'
	     * to the right of any edge between a type in '~set'
	     * and a type in 'set'.
	     */
	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
	    {
		if (TTMaskHasType(&setC, i) && TTMaskHasType(&set, j))
		{
		    plane = LowestMaskBit(pset);

		    /* find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, horizon);
		    dpnew = (DRCCookie *) mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, horizon, dp->drcc_next, &set, &set, why,
			    distance, DRC_AREA|DRC_FORWARD, plane, plane);

		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return (horizon);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcOffGrid --
 *
 * Process an off-grid rule.
 * This is of the form:
 *
 *	off_grid layers pitch why
 *
 * e.g,
 *
 *	off_grid m1 5 "metal shapes must be on %d grid"
 *
 * "pitch" is the grid pitch that shapes must be aligned to.
 *
 * Results:
 *	Returns pitch (the halo for detecting off-grid errors).
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcOffGrid(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    int pitch = atoi(argv[2]);
    int why = drcWhyCreate(argv[3]);
    TileTypeBitMask set, setC;
    DRCCookie *dp, *dpnew;
    TileType i, j;
    PlaneMask pset;
    int plane;

    DBTechNoisyNameMask(layers, &set);
    TTMaskCom2(&setC, &set);

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    /*
	     * Must have types in 'set' for at least 'distance'
	     * to the right of any edge between a type in '~set'
	     * and a type in 'set'.
	     */
	    if ((pset = (DBTypesOnSamePlane(i, j))))
	    {
		if (TTMaskHasType(&setC, i) && TTMaskHasType(&set, j))
		{
		    plane = LowestMaskBit(pset);

		    /* find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, pitch);
		    dpnew = (DRCCookie *) mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, pitch, dp->drcc_next, &set, &set, why,
			    0, DRC_OFFGRID|DRC_FORWARD, plane, plane);
		    dp->drcc_next = dpnew;

		    /* opposite edge also needs to be checked */
		    dp = drcFindBucket(j, i, pitch);
		    dpnew = (DRCCookie *) mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, pitch, dp->drcc_next, &set, &set, why,
			    0, DRC_OFFGRID|DRC_REVERSE, plane, plane);
		    dp->drcc_next = dpnew;

		}
	    }
	}
    }
    return (pitch);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcMaxwidth --
 *
 * Process a maxwidth rule.
 * This is of the form:
 *
 *	maxwidth layers distance [bends] [exclude_layers] why
 *
 * This routine was updated 3/6/05 to match the "canonical" definition of
 * a maxwidth region, which is any rectangle containing <layers> that is
 * at least <distance> in both width and height.  If the keyword
 * "bend_illegal" is present, then the definition reverts to the original
 * (see below) for backwards-compatibility.  Otherwise ("bend_ok" or
 * nothing), the new routine is used.
 *
 * exclude_layers is optional and indicates a type or types (which if more
 * than one must all be on the same plane) that prevent "maxwidth" from
 * being checked.  A common use case is using "glass" (passivation cut) to
 * exclude top metal on a pad from being checked for maximum metal width.
 *
 *	maxwidth metal1 389 "metal1 width > 35um must be slotted"
 *	maxwidth pmc 4 bend_illegal "poly contact area must be no wider than 4"
 *	maxwidth trench 4 bend_ok "trench width must be exactly 4"
 *
 *      bend_illegal - means that one_dimension must be distance for any
 *  		point in the region.  This is used for emitters and contacts
 *		that are rectangular (so we can't generate them with the
 *		squares command) and some exact width in one direction.
 *	bend_ok - Used mainly for wide metal rules where metal greater than
 *		some given width must be slotted.  Also, used for things
 *		like trench, where the width is some fixed value:
 *      both - implies bend_illegal and both directions are checked
 *
 *			XXXXX		XXXXXX
 *			X   X		XXXXXX
 *			X   X		X    X
 *			XXXXX		XXXXXX
 *
 *			 OK		 BAD
 *
 * Results:
 *	Returns distance.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcMaxwidth(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    int distance = atoi(argv[2]);
    char *bends = argv[3];
    int why;
    TileTypeBitMask set, setC, setE;
    DRCCookie *dp, *dpnew;
    TileType i, j;
    PlaneMask pmask, pmask2, ptest, pset;
    int plane, plane2;
    int bend;

    ptest = DBTechNoisyNameMask(layers, &set);
    pmask = CoincidentPlanes(&set, ptest);
    TTMaskCom2(&setC, &set);

    if (pmask == 0)
    {
	TechError("All layers for \"maxwidth\" must be on same plane\n");
	return (0);
    }

    if (argc == 4)
    {
	/* "bends" is irrelevent if distance is zero, so choose the	*/
	/* faster algorithm to process					*/

	if (distance == 0)
	    bend = 0;
	else
	    bend = DRC_BENDS;
	why = drcWhyCreate(argv[3]);
    }
    else
    {
	if (strcmp(bends,"bend_illegal") == 0) bend = 0;
	else if (strcmp(bends,"bend_ok") == 0) bend = DRC_BENDS;
	else if (strcmp(bends,"both") == 0) bend = DRC_MAXWIDTH_BOTH;
	else
	{
	    TechError("unknown bend option %s\n",bends);
	    return (0);
	}
	if (argc == 6)
	    why = drcWhyCreate(argv[5]);
	else
	    why = drcWhyCreate(argv[4]);
    }
    if (argc == 6)
    {
	ptest = DBTechNoisyNameMask(argv[4], &setE);
	pmask2 = CoincidentPlanes(&setE, ptest);
	if (pmask2 == 0)
	{
	    TechError("All layers for \"maxwidth\" exclude types must "
			"be on same plane.\n");
	    return (0);
	}
	else
	{
	    for (plane2 = PL_TECHDEPBASE; plane2 < DBNumPlanes; plane2++)
	        if (PlaneMaskHasPlane(pmask2, plane2))
		    break;

	    if (plane2 == plane)
		TechError("Warning:  Exclude types for \"maxwidth\" are on the "
			"same plane and so cannot be checked.\n");
	}
    }
    else
	plane2 = -1;

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    /*
	     * Must have types in 'set' for at least 'distance'
	     * to the right of any edge between a type in '~set'
	     * and a type in 'set'.
	     */
	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
	    {
		if (TTMaskHasType(&setC, i) && TTMaskHasType(&set, j))
		{
		    plane = LowestMaskBit(pset);

		    /* find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *) mallocMagic(sizeof (DRCCookie));
		    if (plane2 == -1)
			drcAssign(dpnew, distance, dp->drcc_next, &set, &set, why,
				    distance, DRC_MAXWIDTH | bend, plane, plane);
		    else
			drcAssign(dpnew, distance, dp->drcc_next, &set, &setE, why,
				    distance, DRC_MAXWIDTH | bend, plane2, plane);

		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return (distance);
}

/*
 * ----------------------------------------------------------------------------
 * drcAngles --
 *
 * Process an "angles" rule specifying that geometry for a certain layer
 * must be limited to 90 degrees or 45 degrees.  If not specified, any
 * angle is allowed, although width rules will flag errors on acute angles.
 *
 * For all types in "layers" (argv[1]), edges between types on the same
 * plane are considered false edges (e.g., if poly is 90-degrees only,
 * then a transistor-to-poly edge is not considered because it represents
 * an angled edge on diffusion, not poly, assuming that both poly and
 * transistor types appear in the layers list).
 *
 * ----------------------------------------------------------------------------
 */

int
drcAngles(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    char *endptr;
    long value;
    int angles;
    int why = drcWhyCreate(argv[3]);
    TileTypeBitMask set;
    DRCCookie *dp, *dpnew;
    int plane;
    PlaneMask ptest, pset, pmask;
    TileType i, j;

    value = strtol(argv[2], &endptr, 10);

    switch (value)
    {
	case 45:
	    angles = DRC_ANGLES_45 | DRC_ANGLES_90;
	    break;
	case 90:
	    angles = DRC_ANGLES_90;
	    break;
	default:
	    TechError("angles must be 45 or 90\n");
	    return 0;
    }

    if (endptr != NULL)
	if (!strcmp(endptr + 1, "only"))
	    if (angles & DRC_ANGLES_45)
		angles = DRC_ANGLES_45; 	/* 45 degrees only */

    ptest = DBTechNoisyNameMask(layers, &set);

    /* The 45 degrees only case is implemented like drcRectOnly, except	*/
    /* that drcRectOnly checks only for inside corners, while this	*/
    /* also checks for outside corners.  Where inside and outside	*/
    /* corners have been beveled, the error is discarded while		*/
    /* processing.							*/

    if (angles == DRC_ANGLES_45)
    {
	TileTypeBitMask set2, setC, setM;

	pmask = CoincidentPlanes(&set, ptest);

	if (pmask == 0)
	{
	    TechError("All types for \"angles\"  must be on the same plane.\n");
	    return 0;
	}

    	/* set2 is the inverse of set */
	TTMaskCom2(&set2, &set);

	for (i = 0; i < DBNumTypes; i++)
	{
	    for (j = 0; j < DBNumTypes; j++)
	    {
		if (i == j) continue;

		if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
		{
		    /* Inside corners */
		    if (TTMaskHasType(&set, i) && TTMaskHasType(&set2, j))
		    {
			plane = LowestMaskBit(pset);
			/* setC = all types in plane */
			TTMaskZero(&setC);
			TTMaskSetMask(&setC, &DBPlaneTypes[plane]);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, 1);
			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dpnew, 1, dp->drcc_next, &set2, &setC, why, 1,
				angles | DRC_FORWARD | DRC_BOTHCORNERS,
				plane, plane);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, 1);
			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dpnew, 1, dp->drcc_next, &set2, &setC, why, 1,
				angles | DRC_REVERSE | DRC_BOTHCORNERS,
				plane, plane);
			dp->drcc_next = dpnew;
		    }

		    /* Outside corners */
		    if (TTMaskHasType(&set2, i) && TTMaskHasType(&set, j))
		    {
			plane = LowestMaskBit(pset);
			/* setC = all types in plane */
			TTMaskZero(&setC);
			TTMaskSetMask(&setC, &DBPlaneTypes[plane]);

			/* setM = not(i) */
			TTMaskSetOnlyType(&setM, i);
			TTMaskCom(&setM);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, 1);
			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dpnew, 1, dp->drcc_next, &setM, &setC, why, 1,
				angles | DRC_FORWARD | DRC_BOTHCORNERS,
				plane, plane);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, 1);
			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dpnew, 1, dp->drcc_next, &setM, &setC, why, 1,
				angles | DRC_REVERSE | DRC_BOTHCORNERS,
				plane, plane);
			dp->drcc_next = dpnew;
		    }
		}
	    }
	}
    }

    for (i = 0; i < DBNumTypes; i++)
    {
	if (TTMaskHasType(&set, i))
	{
	    plane = DBPlane(i);

	    /* Insert rule at boundary of type and all other types	*/
	    /* in the same plane that do not belong to the layer list.	*/

	    for (j = 0; j < DBNumTypes; j++)
	    {
		if (i == j) continue;
		if ((j != TT_SPACE) && (DBPlane(j) != plane)) continue;

		if (!TTMaskHasType(&set, j))
		{
		    dp = drcFindBucket(j, i, 1);
		    dpnew = (DRCCookie *) mallocMagic((unsigned) (sizeof (DRCCookie)));
		    drcAssign(dpnew, 1, dp->drcc_next, &set, &set, why,
				1, angles, plane, plane);
		    dp->drcc_next = dpnew;

		    dp = drcFindBucket(i, j, 1);
		    dpnew = (DRCCookie *) mallocMagic((unsigned) (sizeof (DRCCookie)));
		    drcAssign(dpnew, 1, dp->drcc_next, &set, &set, why,
				1, angles | DRC_REVERSE, plane, plane);
		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcSpacing3 --
 *
 * Process a special spacing rule of the form:
 *
 *	spacing layers1 layers2 distance corner_ok layers3 why
 *
 * This spacing rule is not checked when a type from "layers3" fills the
 * corner between "layers1" and "layers2".  This is used, e.g., for
 * diffusion-to-poly spacing, where diffusion and poly may touch at the
 * corner of a FET type.  It is equivalent to:
 *
 *	edge4way layers1 ~(layers3,layers1) distance ~(layers2) 0 0 why
 *
 * The complementary case is redundant, so not checked.
 *
 * ----------------------------------------------------------------------------
 */

int
drcSpacing3(argc, argv)
    int argc;
    char *argv[];
{
    char *layers1 = argv[1], *layers2 = argv[2];
    char *layers3 = argv[5];
    int distance = atoi(argv[3]);
    char *adjacency = argv[4];
    int why = drcWhyCreate(argv[6]);
    TileTypeBitMask set1, set2, set3;
    int plane;
    DRCCookie *dp, *dpnew;
    TileType i, j;
    PlaneMask pmask, pset, ptest;

    ptest = DBTechNoisyNameMask(layers1, &set1);
    pmask = CoincidentPlanes(&set1, ptest);

    ptest = DBTechNoisyNameMask(layers2, &set2);
    pmask &= CoincidentPlanes(&set2, ptest);

    ptest = DBTechNoisyNameMask(layers3, &set3);
    pmask &= CoincidentPlanes(&set3, ptest);

    if (pmask == 0)
    {
	TechError("Spacing check with \"corner_ok\" must have"
			" all types in one plane.\n");
	return (0);
    }

    /* In this usage everything must fall in the same plane. */

    /* We need masks for (~types2)/plane and for (~(types1,types3))/plane */

    TTMaskCom(&set2);
    TTMaskSetMask(&set3, &set1);
    TTMaskCom(&set3);

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
	    {
		if (TTMaskHasType(&set1, i) && TTMaskHasType(&set3, j))
		{
		    plane = LowestMaskBit(pset);

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, distance, dp->drcc_next, &set2, &set3,
				why, distance, DRC_FORWARD | DRC_BOTHCORNERS,
				plane, plane);
		    dp->drcc_next = dpnew;

		    /* find bucket preceding new one we wish to insert */
		    dp = drcFindBucket(j, i, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, distance, dp->drcc_next, &set2, &set3,
				why, distance, DRC_REVERSE | DRC_BOTHCORNERS,
				plane, plane);
		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return distance;
}

/*
 *-------------------------------------------------------------------
 *
 * drcMaskSpacing ---
 *
 *	This is the core of the drcSpacing routine.  When the spacing
 *	rule layers independently cover more than one plane, this routine
 *	is invoked for each independent plane.
 *
 * Results:
 *	Returns the rule's maximum distance
 *
 * Side effects:
 *	Adds rules to the DRC rule table.
 *
 * Notes:
 *	8/13/2025:  Added "manhattan_dist" as an option indicating
 *	that corner checks should assume manhattan distance.  Otherwise
 *	the rule type is the same as "touching_illegal".
 *-------------------------------------------------------------------
 */

int
drcMaskSpacing(set1, set2, pmask1, pmask2, wwidth, distance, adjacency,
		why, widerule, runlength, multiplane)
    TileTypeBitMask *set1, *set2;
    PlaneMask pmask1, pmask2;
    int wwidth, distance;
    char *adjacency;
    int why;
    bool widerule;
    int runlength;
    bool multiplane;
{
    TileTypeBitMask tmp1, tmp2, setR, setRreverse;
    int plane, plane2;
    PlaneMask pset, ptest;
    DRCCookie *dp, *dpnew;
    int needReverse = (widerule) ? TRUE : FALSE;
    TileType i, j, pref;
    bool needtrigger = FALSE;
    bool touchingok = TRUE;
    bool cornerok = FALSE;
    bool surroundok = FALSE;
    unsigned short flags = 0;

    if (!strcmp(adjacency, "surround_ok"))
    {
	if (multiplane)
	{
	    TechError("\"surround_ok\" requires surrounding types to "
		"be in the same plane.\n");
	    return (0);
	}
	else if ((pmask1 & pmask2) == 0)
	{
	    /* New rule implementation (7/8/06):  When types	*/
	    /* are on different planes and "surround_ok" is	*/
	    /* declared, implement as a triggered rule (but not	*/
	    /* for widespacing, which already uses the		*/
	    /* triggering rule mechanism).			*/

	    if (!widerule)
	    {
		needtrigger = TRUE;
		touchingok = FALSE;
		needReverse = TRUE;
		surroundok = TRUE;
	    }
	    else
	    {
		TechError("Widespacing checks cannot use \"surround_ok\".\n");
		return (0);
	    }
	}
	else
	{
	    TechError("\"surround_ok\" used when spacing rule types are in "
			"the same plane.  Did you mean \"touching_ok\"?\n");
	    touchingok = TRUE;	/* Treat like "touching_ok" */
	}
    }
    else if (!strcmp(adjacency, "touching_ok"))
    {
	/* If touching is OK, everything must fall in the same plane. */
	if (multiplane || ((pmask1 & pmask2) == 0))
	{
	    TechError("Spacing check with \"touching_ok\" must have"
			" all types in one plane.  Possibly you want"
			" \"surround_ok\"?\n");
	    return (0);
	}
	else
	    touchingok = TRUE;
    }
    else if (!strcmp(adjacency, "touching_illegal"))
    {
	touchingok = FALSE;
	needtrigger = FALSE;
    }
    else if (!strcmp(adjacency, "manhattan_dist"))
    {
	touchingok = FALSE;
	needtrigger = FALSE;
	flags = DRC_MANHATTAN;
    }
    else
    {
	TechError("Badly formed drc spacing line:  need \"touching_ok\", "
			"\"touching_illegal\", or \"surround_ok\".\n");
	return (0);
    }

    if (touchingok)
    {
	/* In "touching_ok rules, spacing to set2  is be checked in FORWARD
	 * direction at edges between set1 and  (setR = ~set1 AND ~set2).
	 *
	 * In addition, spacing to set1 is checked in FORWARD direction
	 * at edges between set2 and (setRreverse = ~set1 AND ~set2).
	 *
	 * If set1 and set2 are different, above are checked in REVERSE as
	 * well as forward direction.  This is important since touching
	 * material frequently masks violations in one direction.
	 *
	 * setR and setRreverse are set appropriately below.
	 */

	tmp1 = *set1;
	tmp2 = *set2;

	/* Restrict planes to those that are coincident */
	pmask1 &= pmask2;
	pmask2 = pmask1;
	TTMaskCom(&tmp1);
	TTMaskCom(&tmp2);
	TTMaskAndMask(&tmp1, &tmp2);
	setR = tmp1;
	setRreverse = tmp1;

	/* If set1 != set2, set flag to check rules in both directions */
	if (!TTMaskEqual(set1, set2))
	    needReverse = TRUE;
    }
    else
    {
	/* In "touching_illegal" rules, spacing to set2 will be checked
	 * in FORWARD direction at edges between set1 and (setR=~set1).
	 *
	 * In addition, spacing to set1 will be checked in FORWARD direction
	 * at edges between set2 and (setRreverse=  ~set2).
	 *
	 * setR and setRreverse are set appropriately below.
	 */
	TTMaskCom2(&setR, set1);
	TTMaskCom2(&setRreverse, set2);
    }

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask1)))
	    {
		plane = LowestMaskBit(pset);

		/* LHS is an element of set1, RHS is an element of setR */
		if (TTMaskHasType(set1, i) && TTMaskHasType(&setR, j))
		{
		    plane2 = LowestMaskBit(pmask2);

		    /*
		     * Must not have 'set2' for 'distance' to the right of
		     * an edge between 'set1' and the types not in 'set1'
		     * (touching_illegal case) or in neither
		     * 'set1' nor 'set2' (touching_ok case).
		     */

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);

		    dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
		    TTMaskClearMask3(&tmp1, &DBPlaneTypes[plane2], set2);
		    TTMaskAndMask3(&tmp2, &DBPlaneTypes[plane], &setR);

		    if (widerule)
		    {
			DRCCookie *dptrig;

			/* Create two contiguous rules, one for spacing	*/
			/* and one for width.  These are created in	*/
			/* reverse order due to the stack property of	*/
			/* the linked list.				*/

			if (runlength > 0)
			{
			    drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
					why, runlength, DRC_RUNLENGTH |
					DRC_FORWARD, plane2, plane);
			    dptrig = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
			    drcAssign(dptrig, wwidth, dpnew, set1, set1, why,
					runlength, DRC_REVERSE | DRC_MAXWIDTH |
					DRC_TRIGGER | DRC_BENDS, plane2, plane);
			}
			else
			{
			    drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
					why, distance, DRC_FORWARD, plane2, plane);
			    dptrig = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
			    drcAssign(dptrig, wwidth, dpnew, set1, set1, why,
					distance, DRC_REVERSE | DRC_MAXWIDTH |
					DRC_TRIGGER | DRC_BENDS, plane2, plane);
			}

			dp->drcc_next = dptrig;
		    }
		    else if (needtrigger)
		    {
			DRCCookie *dptrig;

			/* Create two contiguous spacing rules */

			drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
			 	why, wwidth, DRC_FORWARD | DRC_BOTHCORNERS,
				plane2, plane);
			dptrig = (DRCCookie *) mallocMagic((unsigned)
				(sizeof (DRCCookie)));
			drcAssign(dptrig, 1, dpnew, set2, &tmp2, why, 1,
			 	DRC_FORWARD | DRC_TRIGGER,
				plane2, plane);

			dp->drcc_next = dptrig;
		    }
		    else
		    {
			drcAssign(dpnew, distance, dp->drcc_next, &tmp1,
				&tmp2, why, wwidth, DRC_FORWARD | flags, plane2, plane);
			dp->drcc_next = dpnew;
		    }

		    if (needReverse)
			dpnew->drcc_flags |= DRC_BOTHCORNERS;

		    if (needReverse)
		    {
			/* Add check in reverse direction,
			 * NOTE:  am assuming single plane rule here (since reverse
			 * rules only used with touching_ok which must be
			 * single plane)
			 */

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			if (widerule)
			{
			    DRCCookie *dptrig;

			    /* Assign two coupled rules (see above) */

			    if (runlength > 0)
			    {
				drcAssign(dpnew, distance, dp->drcc_next, &tmp1,
					&tmp2, why, runlength, DRC_RUNLENGTH |
					DRC_REVERSE | DRC_BOTHCORNERS, plane2, plane);
				dptrig = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
				drcAssign(dptrig, wwidth, dpnew, set1, set1, why,
					runlength, DRC_FORWARD | DRC_MAXWIDTH |
					DRC_TRIGGER | DRC_BENDS, plane2, plane);
			    }
			    else
			    {
				drcAssign(dpnew, distance, dp->drcc_next, &tmp1,
					&tmp2, why, distance,
					DRC_REVERSE | DRC_BOTHCORNERS, plane2, plane);
				dptrig = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
				drcAssign(dptrig, wwidth, dpnew, set1, set1, why,
					distance, DRC_FORWARD | DRC_MAXWIDTH |
					DRC_TRIGGER | DRC_BENDS, plane2, plane);
			    }
			    dp->drcc_next = dptrig;
			}
			else if (needtrigger)
			{
			    DRCCookie *dptrig;

			    /* Create two contiguous spacing rules */

			    drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
			 		why, wwidth, DRC_REVERSE | DRC_BOTHCORNERS,
					plane2, plane);
			    dptrig = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
			    drcAssign(dptrig, 1, dpnew, set2, &tmp2, why, 1,
			 		DRC_REVERSE | DRC_TRIGGER,
					plane2, plane);

			    dp->drcc_next = dptrig;
			}
			else
			{
			    drcAssign(dpnew,distance,dp->drcc_next,
					&tmp1, &tmp2, why, wwidth,
					DRC_REVERSE | DRC_BOTHCORNERS | flags,
					plane2, plane);
			    dp->drcc_next = dpnew;
		 	}
		    }
		}
	    }

	    if (TTMaskEqual(set1, set2)) continue;
	    if (widerule) continue;	/* Can't determine width of set1    */
					/* when looking at the edge of set2 */

	    /*
	     * Now, if set1 and set2 are distinct apply the rule for LHS in set1
	     * and RHS in set2.  HOWEVER, "surround_ok" rules are asymmetrically
	     * triggered and cannot be reversed between set1 and set2.
	     */

	    if ((!surroundok) && (pset = (DBTypesOnSamePlane(i, j) & pmask2)))
	    {
		plane = LowestMaskBit(pset);

		/* LHS is an element of set2, RHS is an element of setRreverse */
		if (TTMaskHasType(set2, i) && TTMaskHasType(&setRreverse, j))
		{
		    plane2 = LowestMaskBit(pmask1);

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    TTMaskClearMask3(&tmp1, &DBPlaneTypes[plane2], set1);
		    TTMaskAndMask3(&tmp2, &DBPlaneTypes[plane], &setRreverse);

		    /* NOTE:  This is needed for some situation, but I	*/
		    /* do not recall the exact nature of it.  In other	*/
		    /* cases only the simple rule check is needed.	*/
		    if (needtrigger)
		    {
			DRCCookie *dptrig;

			/* Create two contiguous spacing rules */

		        drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
					why, distance, DRC_FORWARD | DRC_BOTHCORNERS,
					plane2, plane);
			dptrig = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dptrig, 1, dpnew, set1, &tmp2, why, 1,
			 		DRC_FORWARD | DRC_TRIGGER,
					plane2, plane);
			dp->drcc_next = dptrig;
		    }
		    else
		    {
			drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
					why, distance, DRC_FORWARD | flags,
					plane2, plane);
			dp->drcc_next = dpnew;
		    }

		    if (needReverse)
			dpnew->drcc_flags |= DRC_BOTHCORNERS;

		    if (needReverse)
		    {
			/* Add check in reverse direction,
			 * NOTE:  am assuming single plane rule here (since reverse
			 * rules only used with touching_ok which must be
			 * single plane)
			 */
			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *) mallocMagic(sizeof (DRCCookie));

			/* See above */
			if (needtrigger)
			{
			    DRCCookie *dptrig;

			    /* Create two contiguous spacing rules */

		            drcAssign(dpnew, distance, dp->drcc_next, &tmp1, &tmp2,
					why, distance, DRC_REVERSE | DRC_BOTHCORNERS,
					plane2, plane);
			    dptrig = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			    drcAssign(dptrig, 1, dpnew, set1, &tmp2, why, 1,
			 		DRC_REVERSE | DRC_TRIGGER,
					plane2, plane);
			    dp->drcc_next = dptrig;
			}
			else
			{
			    drcAssign(dpnew, distance, dp->drcc_next,
					&tmp1, &tmp2, why, distance,
					DRC_REVERSE | DRC_BOTHCORNERS | flags,
					plane2, plane);
			    dp->drcc_next = dpnew;
			}
		    }
		}
	    }

	    /* Finally, if multiplane rule then check that set2 types
	     * are not present just to right of edges with setR on LHS
	     * and set1 on RHS.  This check is necessary to make sure
	     * that a set1 rectangle doesn't coincide exactly with a
	     * set2 rectangle.
	     * (This check added by Michael Arnold on 4/10/86.)
	     */

	    if (needtrigger) continue;

	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask1)))
	    {
		plane = LowestMaskBit(pset);

		/* LHS is an element of setR, RHS is an element of set1 */
		if (TTMaskHasType(&setR, i) && TTMaskHasType(set1, j))
		{
		    /*
		     * Must not have 'set2' for 'distance' to the right of
		     * an edge between the types not in set1 and set1.
		     * (is only checked for cross plane rules - these are
		     * all of type touching_illegal)
		     */

		    /* Walk list to last check.  New checks ("cookies") go
		     * at end of list since we are checking for distance of
		     * 1 and the list is sorted in order of decreasing distance.
		     */
		    for (dp = DRCCurStyle->DRCRulesTbl [i][j];
		    		dp->drcc_next != (DRCCookie *) NULL;
		    		dp = dp->drcc_next); /* null body */

		    /* Insert one check for each plane involved in set2 */
		    plane2 = LowestMaskBit(pmask2);

		    /* filter out checks that are not cross plane */
		    if (i == TT_SPACE)
		    {
			if (DBTypeOnPlane(j, plane2))
			    continue;
		    }
		    else
		    {
			if (DBTypeOnPlane(i, plane2))
			    continue;
		    }

		    /* create new check and add it to list */
		    dpnew = (DRCCookie *) mallocMagic(sizeof (DRCCookie));
		    TTMaskClearMask3(&tmp1, &DBPlaneTypes[plane2], set2);
		    TTMaskZero(&tmp2);

		    drcAssign(dpnew, 1, dp->drcc_next, &tmp1, &tmp2, why,
				distance, DRC_FORWARD | flags, plane2, plane);
		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return (MAX(wwidth, distance));
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcSpacing --
 *
 * Process a spacing rule.
 * This is of the form:
 *
 *	spacing layers1 layers2 distance adjacency why
 *
 * e.g,
 *
 *	spacing metal,pmc/m,dmc/m metal,pmc/m,dmc/m 4 touching_ok \
 *		"metal spacing must be at least 4"
 *
 * Adjacency may be either "touching_ok" or "touching_illegal"
 * In the first case, no violation occurs when types in layers1 are
 * immediately adjacent to types in layers2.  In the second case,
 * such adjacency causes a violation.
 *
 * Results:
 *	Returns distance.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * Notes:
 * Extended to include the rule syntax:
 *
 *	widespacing layers1 width [runlength] layers2 distance adjacency why
 *
 * This extension covers rules such as "If m1 width > 10um, then spacing to
 * unconnected m1 must be at least 0.6um".  This assumes that the instantiated
 * edge4way rule is a standard "spacing" rule in which "dist" and "cdist"
 * (corner extension) distances are always the same, so we can use the "cdist"
 * record to encode the width of "layers1" which triggers the rule.  We re-use
 * the CheckMaxwidth() code, but with the following differences: 1) it does not
 * trigger any error painting functions, 2) it returns a value stating whether
 * the maxwidth rule applies or not, 3) it uses the DBLayerTypeMaskTbl[] for
 * the tile type in question, not "oktypes", for its search, and 4) it uses
 * the max width in the "cdist" record, not the "dist" record, for the max
 * width rule.  The "dist" record is copied to "cdist" before actually applying
 * the spacing rule, so that the rule acts like a proper spacing rule.
 *
 * Added adjacency rule "surround_ok" to check spacing to a type that
 * may also abut or surround the first layer---the portion of the layer
 * that is abutting or surrounding is not checked.  This allows checks
 * of, e.g., diffusion in well to a separate well edge, or distance from
 * a poly bottom plate to unconnected poly.
 *
 * (Added 11/6/06) Adjacency may be "corner_ok", in which case it calls
 * routing drcSpacing3() (see above).
 *
 * (Added 9/19/18) Additional run-length distance can be used to limit the
 * maxwidth rule to geometry exceeding the given run-length (often used with
 * 65nm and smaller feature size processes).
 *
 * ----------------------------------------------------------------------------
 */

int
drcSpacing(argc, argv)
    int argc;
    char *argv[];
{
    char *layers1 = argv[1], *layers2;
    char *adjacency;
    int why;
    TileTypeBitMask set1, set2, tmp1, tmp2;
    PlaneMask pmask1, pmask2, pmaskA, pmaskB, ptest;
    int wwidth, distance, plane, plane2, runlength;
    bool widerule, multiplane = FALSE;

    if ((argc == 7) && (!strcmp(argv[4], "corner_ok")))
	return drcSpacing3(argc, argv);

    widerule = (strncmp(argv[0], "wide", 4) == 0);

    if (widerule)
    {
	wwidth = atoi(argv[2]);

	if (argc == 8)
	{
	    runlength = atoi(argv[3]);
	    layers2 = argv[4];
	    distance = atoi(argv[5]);
	    adjacency = argv[6];
	    why = drcWhyCreate(argv[7]);
	}
	else
	{
	    layers2 = argv[3];
	    distance = atoi(argv[4]);
	    runlength = -1;
	    adjacency = argv[5];
	    why = drcWhyCreate(argv[6]);
	}
	/* TxPrintf("Info:  DRCtech:  widespacing rule for %s width %d:"
		" spacing must be %d\n", layers1, wwidth, distance); */
    }
    else
    {
	layers2 = argv[2];
	distance = atoi(argv[3]);
	adjacency = argv[4];
	wwidth = distance;
	why = drcWhyCreate(argv[5]);
	runlength = -1;
	if (argc >= 7)
	{
	    TechError("Unknown argument in spacing line.\n");
	    return(0);
	}
    }

    /* Either list of types may contain independent types on different	*/
    /* planes.  However, if so, then we may not use touching_ok, and	*/
    /* there are other restrictions.					*/

    ptest = DBTechNoisyNameMask(layers1, &set1);
    pmask1 = CoincidentPlanes(&set1, ptest);

    if ((pmask1 == 0) && (ptest != 0))
    {
	pmask1 = ptest;
	multiplane = TRUE;
	for (plane = 0; plane < DBNumPlanes; plane++)
	    for (plane2 = 0; plane2 < DBNumPlanes; plane2++)
	    {
		if (plane == plane2) continue;
		if (PlaneMaskHasPlane(pmask1, plane) &&
			PlaneMaskHasPlane(pmask1, plane2))
		{
		    TTMaskAndMask3(&tmp1, &DBPlaneTypes[plane],
				&DBPlaneTypes[plane2]);
		    TTMaskAndMask(&tmp1, &set1);
		    if (!TTMaskIsZero(&tmp1))
		    {
			TechError("Types in first list must either be in"
				" one plane or else types must not share"
				" planes.\n");
			return (0);
		    }
		}
	    }
    }

    ptest = DBTechNoisyNameMask(layers2, &set2);
    pmask2 = CoincidentPlanes(&set2, ptest);

    if ((pmask2 == 0) && (ptest != 0))
    {
	pmask2 = ptest;
	multiplane = TRUE;
	for (plane = 0; plane < DBNumPlanes; plane++)
	    for (plane2 = 0; plane2 < DBNumPlanes; plane2++)
	    {
		if (plane == plane2) continue;
		if (PlaneMaskHasPlane(pmask2, plane) &&
			PlaneMaskHasPlane(pmask2, plane2))
		{
		    TTMaskAndMask3(&tmp1, &DBPlaneTypes[plane],
				&DBPlaneTypes[plane2]);
		    TTMaskAndMask(&tmp1, &set2);
		    if (!TTMaskIsZero(&tmp1))
		    {
			TechError("Types in second list must either be in"
				" one plane or else types must not share"
				" planes.\n");
			return (0);
		    }
		}
	    }
    }

    if (multiplane)
    {
	/* Loop over independent plane/layer combinations */

	for (plane = 0; plane < DBNumPlanes; plane++)
	    for (plane2 = 0; plane2 < DBNumPlanes; plane2++)
	    {
		if (PlaneMaskHasPlane(pmask1, plane) &&
			PlaneMaskHasPlane(pmask2, plane2))
		{
		    pmaskA = PlaneNumToMaskBit(plane);
		    pmaskB = PlaneNumToMaskBit(plane2);

		    TTMaskAndMask3(&tmp1, &set1, &DBPlaneTypes[plane]);
		    TTMaskAndMask3(&tmp2, &set2, &DBPlaneTypes[plane2]);

		    return drcMaskSpacing(&tmp1, &tmp2, pmaskA, pmaskB,
				wwidth, distance, adjacency, why,
				widerule, runlength, multiplane);
		}
	    }
    }
    else
	return drcMaskSpacing(&set1, &set2, pmask1, pmask2, wwidth,
		distance, adjacency, why, widerule, runlength, multiplane);

    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcEdge --
 *
 * Process a primitive edge rule.
 * This is of the form:
 *
 *	edge layers1 layers2 dist OKtypes cornerTypes cornerDist [option] why [plane]
 * or	edge4way layers1 layers2 dist OKtypes cornerTypes cornerDist [option] why [plane]
 *
 * e.g,
 *
 *	edge poly,pmc s 1 diff poly,pmc "poly-diff separation must be 2"
 *
 * An "edge" rule is applied only down and to the left.
 * An "edge4way" rule is applied in all four directions.
 *
 * "option" can be "manhattan_dist", which forces the corner areas to be
 * checked per manhattan distance rules, not euclidean distance rules.
 *
 * Results:
 *	Returns greater of dist and cdist.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcEdge(argc, argv)
    int argc;
    char *argv[];
{
    char *layers1 = argv[1], *layers2 = argv[2];
    int distance = atoi(argv[3]);
    char *okTypes = argv[4], *cornerTypes = argv[5];
    int cdist = atoi(argv[6]);
    unsigned short flags = 0;
    int why;
    bool fourway = (strcmp(argv[0], "edge4way") == 0);
    TileTypeBitMask set1, set2, setC, setM;
    DRCCookie *dp, *dpnew;
    int plane, checkPlane, tmpPlane = 0;
    PlaneMask pMask1, pMaskM, pMaskC, pset, ptest;
    TileType i, j;

    if ((argc > 7) && (!strcmp(argv[7], "manhattan_dist")))
    {
	flags = DRC_MANHATTAN;
	why = drcWhyCreate(argv[8]);
	if (argc == 10)
	    tmpPlane = DBTechNoisyNamePlane(argv[9]);
	argc--;
    }
    else
    {
	why = drcWhyCreate(argv[7]);
	if (argc == 9)
	    tmpPlane = DBTechNoisyNamePlane(argv[8]);
    }

    /*
     * Edge4way rules produce [j][i] entries as well as [i][j]
     * ones, and check both corners rather than just one corner.
     */

    ptest = DBTechNoisyNameMask(layers1, &set1);
    pMask1 = CoincidentPlanes(&set1, ptest);

    ptest = DBTechNoisyNameMask(layers2, &set2);
    pMask1 &= CoincidentPlanes(&set2, ptest);

    if (pMask1 == 0)
    {
	TechError("All edges in edge rule must lie in shared planes.\n");
	return (0);
    }

    /* Give warning if types1 and types2 intersect */
    if (TTMaskIntersect(&set1, &set2))
	TechError("Warning:  types1 and types2 have nonempty intersection.  "
		"DRC does not check edges with the same type on both "
		"sides.\n");

    ptest = DBTechNoisyNameMask(cornerTypes, &setC);
    pMaskC = CoincidentPlanes(&setC, ptest);

    if ((pMaskC & pMask1) == 0)
    {
	TechError("Corner types aren't in same plane as edges.\n");
	return (0);
    }

    /*
     * OKtypes determine the checkPlane.  If checkPlane exists, it should
     * only be used to check against the plane of OKtypes.
     */

    ptest = DBTechNoisyNameMask(okTypes, &setM);
    pMaskM = CoincidentPlanes(&setM, ptest);

    if (pMaskM == 0 || pMaskM == DBTypePlaneMaskTbl[TT_SPACE])
    {
	/* Technically it should be illegal to specify simply "space"
	 * in the types list for a DRC rule, as it is ambiguous.
	 * However, we will assume that the plane of the edge is
	 * intended.  The "plane" argument may be used to do the
	 * qualification (for backwards compatibility); any other use
	 * gets a warning.
	 */

	if (TTMaskEqual(&DBSpaceBits, &setM))
	{
	    if (argc == 9)
		pMaskM = PlaneNumToMaskBit(tmpPlane);
	    else
	    {
		TechError("OK types \"%s\" in more than one plane.\n"
	        	"	Assuming same plane (%s) as edge.\n",
			okTypes, DBPlaneLongNameTbl[LowestMaskBit(pMask1)]);
		pMaskM = pMask1;
	    }
	}

	/* The case okTypes="0" is explicitly allowed according */
	/* to the manual, so we parse it accordingly.		*/

	else if (!strcmp(okTypes, "0"))
	    pMaskM = pMask1;
	else
	{
	    TechError("All OK types must lie in one plane.\n");
	    return (0);
	}
    }

    /* "plane" argument deprecated; kept for backward compatibility only */

    if ((argc == 9) && (PlaneNumToMaskBit(tmpPlane) != pMaskM))
	    TechError("Ignoring bad plane argument.\n");

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    if ((pset = (DBTypesOnSamePlane(i, j) & pMask1)))
	    {
		if (TTMaskHasType(&set1, i) && TTMaskHasType(&set2, j))
		{
		    /* For fastest DRC, checkPlane and plane should be	*/
		    /* the same, if possible.				*/

		    if ((pset & pMaskM) != 0)
		    {
			plane = LowestMaskBit(pset & pMaskM);
			checkPlane = plane;
		    }
		    else
		    {
			plane = LowestMaskBit(pset);
			checkPlane = LowestMaskBit(pMaskM);
		    }

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, distance, dp->drcc_next, &setM, &setC,
				why, cdist, DRC_FORWARD | flags, checkPlane, plane);
		    if (fourway) dpnew->drcc_flags |= DRC_BOTHCORNERS;
		    dp->drcc_next = dpnew;

		    if (!fourway) continue;

		    /* find bucket preceding new one we wish to insert */
		    dp = drcFindBucket(j, i, distance);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew,distance,dp->drcc_next, &setM, &setC,
				why, cdist, DRC_REVERSE | flags, checkPlane, plane);
		    dpnew->drcc_flags |= DRC_BOTHCORNERS;
		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return (MAX(distance, cdist));
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcOverhang --
 *
 * Process an overhang rule.
 * This is of the form:
 *
 *	overhang layers2 layers1 dist why
 *
 * indicating that layers2 must overhang layers1 by a distance of at least
 * dist.
 *
 * This rule is equivalent to:
 *
 *	edge4way layers1 space/p2|layers2 dist layers1|layers2 \
 *			 space/p2|layers2 dist why
 *
 * ----------------------------------------------------------------------------
 */

int
drcOverhang(argc, argv)
    int argc;
    char *argv[];
{
    char *layers2 = argv[1], *layers1 = argv[2];
    int distance = atoi(argv[3]);
    int why = drcWhyCreate(argv[4]);
    TileTypeBitMask set1, set2, setM, setC, setN, set2inv;
    DRCCookie *dp, *dpnew, *dptrig;
    int plane, plane2;
    TileType i, j;
    PlaneMask pMask1, pMask2, pset, ptest;

    ptest = DBTechNoisyNameMask(layers1, &set1);
    pMask1 = CoincidentPlanes(&set1, ptest);
    if (pMask1 == 0)
    {
	TechError("All layers in first set for \"overhang\" must be on "
			"the same plane\n");
	return (0);
    }
    TTMaskCom2(&setN, &set1);

    ptest = DBTechNoisyNameMask(layers2, &set2);
    pMask2 = CoincidentPlanes(&set2, ptest);
    if (pMask2 == 0)
    {
	TechError("All layers in second set for \"overhang\" must be on "
			"the same plane\n");
	return (0);
    }
    TTMaskCom2(&set2inv, &set2);

    /* Warn if types1 and types2 intersect */
    if (TTMaskIntersect(&set1, &set2))
	TechError("Warning:  inside and outside types have nonempty intersection.  "
		"DRC does not check edges with the same type on both sides.\n");

    /* SetM is the union of set1 and set2 */
    TTMaskZero(&setM);
    TTMaskSetMask3(&setM, &set1, &set2);

    /* Add space to set2 */
    TTMaskSetType(&set2, TT_SPACE);

    /* SetC is the empty set */
    TTMaskZero(&setC);

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;
	    if ((pset = (DBTypesOnSamePlane(i, j) & pMask2)))
	    {
		if ((pset & pMask1) != 0)
		{
		    if (TTMaskHasType(&set1, i) && TTMaskHasType(&set2, j))
		    {
			plane = LowestMaskBit(pset);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic (sizeof (DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &setM,
					&setM, why, distance,
					DRC_FORWARD | DRC_BOTHCORNERS,
					plane, plane);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next,
					&setM, &setM, why, distance,
					DRC_REVERSE | DRC_BOTHCORNERS,
					plane, plane);
			dp->drcc_next = dpnew;
		    }
		}
		else	/* Multi-plane overhang rule */
		{
		    if (TTMaskHasType(&set2, i) && TTMaskHasType(&set2inv, j))
		    {
			plane = LowestMaskBit(pset);
			plane2 = LowestMaskBit(pMask1);

			/* find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &set1, &set1, why,
				    distance, DRC_FORWARD, plane2, plane);
			dptrig = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dptrig, 1, dpnew, &setN, &setC, why,
				    0, DRC_FORWARD | DRC_TRIGGER, plane2, plane);
			dp->drcc_next = dptrig;

			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &set1, &set1, why,
				    distance, DRC_REVERSE, plane2, plane);
			dptrig = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dptrig, 1, dpnew, &setN, &setC, why,
				    0, DRC_REVERSE | DRC_TRIGGER, plane2, plane);
			dp->drcc_next = dptrig;
		    }
		}
	    }
	}
    }
    return distance;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcRectOnly --
 *
 * Process a rectangle-only rule.  This rule prohibits non-rectangular
 * geometry, and is used especially for contacts, as the "squares" operator
 * in the CIF/GDS output generator can't handle non-rectangular areas.
 * The rule is of the form:
 *
 *	rect_only layers why
 *
 * and is equivalent to:
 *
 *	edge4way layers ~(layers)/plane 1 ~(layers)/plane (all_layers)/plane 1
 *
 * The rect_only rule avoids the above contrived construction, especially the
 * requirement of specifying "all_layers" as something like (~(x),x)/p, a sure-
 * fire obfuscation.
 *
 * ----------------------------------------------------------------------------
 */

int
drcRectOnly(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    int why = drcWhyCreate(argv[2]);
    TileTypeBitMask set1, set2, setC;
    PlaneMask pmask, pset, ptest;
    DRCCookie *dp, *dpnew;
    int plane;
    TileType i, j;

    ptest = DBTechNoisyNameMask(layers, &set1);
    pmask = CoincidentPlanes(&set1, ptest);

    if (pmask == 0)
    {
	TechError("All types for \"rect_only\"  must be on the same plane.\n");
	return (0);
    }

    /* set2 is the inverse of set1 */
    TTMaskCom2(&set2, &set1);

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;

	    if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
	    {
		if (TTMaskHasType(&set1, i) && TTMaskHasType(&set2, j))
		{
		    plane = LowestMaskBit(pset);
		    /* setC = all types in plane */
		    TTMaskZero(&setC);
		    TTMaskSetMask(&setC, &DBPlaneTypes[plane]);

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, 1);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew, 1, dp->drcc_next, &set2, &setC, why, 1,
				DRC_FORWARD | DRC_BOTHCORNERS, plane, plane);
		    dp->drcc_next = dpnew;

		    /* find bucket preceding new one we wish to insert */
		    dp = drcFindBucket(j, i, 1);
		    dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
		    drcAssign(dpnew,1,dp->drcc_next, &set2, &setC, why, 1,
				DRC_REVERSE | DRC_BOTHCORNERS, plane, plane);
		    dp->drcc_next = dpnew;
		}
	    }
	}
    }
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcSurround --
 *
 * Process a surround rule.
 * This is of the form:
 *
 *	surround layers1 layers2 dist [dist2] presence why
 *
 * indicating that layers2 must surround layers1 by at least distance
 * dist in all directions.
 *
 * This rule is equivalent to:
 *
 *	edge4way ~(layers2)/plane2 layers2 dist ~(layers1)/plane1 \
 *		layers2 dist why
 *
 * When presence=absence_illegal, the following additional rule is needed:
 *
 *	edge4way layers1 ~(layers2)/plane1 dist NULL ~(layers2)/plane1 \
 *		dist why
 *
 * Extension added July 12, 2014:  For via rules where an asymmetric
 * surround is allowed, with a smaller surround allowed on two sides if
 * the remaining two sides have a larger surround.  This can be implemented
 * with a trigger rule, and is specified by the syntax above with "presence"
 * being "directional".  Note that the rule expresses that the overhang rule
 * requires the presence of the material on one side of a corner.  If the
 * other side has a non-zero minimum surround requirement, then it should
 * be implemented with an additional (absence_illegal) surround rule.
 * Otherwise, any width of material less than "dist" on one side of a
 * corner will trigger the rule requiring at least "dist" width of the same
 * material on the other side of the corner.
 *
 * Extension added November 30, 2022:  Similar to the asymmetric via
 * extension rule above are rules of the type "extension on one side
 * must be >= dist if the extension on the adjacent side is < dist2",
 * for which the asymmetric rule above is the limiting case dist = dist2.
 * dist2 is optional and is taken to be equal to dist if not present,
 * which is the original form of the "directional" rule.
 *
 * ----------------------------------------------------------------------------
 */

int
drcSurround(argc, argv)
    int argc;
    char *argv[];
{
    char *layers1 = argv[1], *layers2 = argv[2], *endptr;
    int distance = atoi(argv[3]);
    int distance2;
    char *presence = argv[4];
    int why = drcWhyCreate(argv[5]);
    TileTypeBitMask set1, set2, setM, invM, setR;
    DRCCookie *dp, *dpnew, *dptrig;
    int plane1, plane2;
    PlaneMask pmask, pmask2, pset, ptest;
    TileType i, j;
    bool isExact = FALSE;
    bool isDirectional = FALSE;

    if (argc == 7)
    {
	distance2 = strtol(argv[4], &endptr, 10);
	if ((endptr != NULL) && (*endptr != '\0'))
	{
	    TechError("Incorrect arguments in directional \"surround\" rule\n");
	    return (0);
	}
	presence = argv[5];
	why = drcWhyCreate(argv[6]);
    }
    else
    {
	distance2 = distance;
	presence = argv[4];
	why = drcWhyCreate(argv[5]);
    }

    ptest = DBTechNoisyNameMask(layers1, &setM);
    pmask = CoincidentPlanes(&setM, ptest);
    if (pmask == 0)
    {
	TechError("Inside types in \"surround\" must be on the same plane\n");
	return (0);
    }

    ptest = DBTechNoisyNameMask(layers2, &set2);
    pmask2 = CoincidentPlanes(&set2, ptest);
    if (pmask2 == 0)
    {
	TechError("Outside types in \"surround\" must be on the same plane\n");
	return (0);
    }

    /* "exact_width" rule implemented 9/16/10.  This enforces an exact	*/
    /* surround distance.  "absence_illegal" is implied.  		*/

    if (!strncmp(presence, "exact_", 6)) isExact = TRUE;
    else if (!strncmp(presence, "directional", 11))
    {
	isDirectional = TRUE;
	/* Combined mask */
	TTMaskZero(&setR);
	TTMaskSetMask(&setR, &setM);
	TTMaskSetMask(&setR, &set2);
    }

    /* invert setM */
    TTMaskCom2(&invM, &setM);

    /* set1 is the inverse of set2 */
    TTMaskCom2(&set1, &set2);

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;	/* Ignore false edges */
	    
	    if (isDirectional)
	    {
		if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
	    	{
		    /* Directional surround is done entirely differently */

		    if (TTMaskHasType(&setM, i) && TTMaskHasType(&invM, j))
		    {
			plane1 = LowestMaskBit(pset);
			plane2 = LowestMaskBit(pmask2);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));

			/* Insert triggered rule */
			drcAssign(dpnew, distance, dp->drcc_next, &setR,
				&DBAllTypeBits,
				why, distance,
				DRC_REVERSE | DRC_BOTHCORNERS,
				plane2, plane1);
			dptrig = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dptrig, distance2, dpnew, &set2,
				&DBZeroTypeBits, why, 0,
				DRC_FORWARD | DRC_TRIGGER,
				plane2, plane1);
			dp->drcc_next = dptrig;

			/* And the other direction. . . */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));

			/* Insert triggered rule */
			drcAssign(dpnew, distance, dp->drcc_next, &setR,
				&DBAllTypeBits,
				why, distance,
				DRC_FORWARD | DRC_BOTHCORNERS,
				plane2, plane1);
			dptrig = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dptrig, distance2, dpnew, &set2,
				&DBZeroTypeBits, why, 0,
				DRC_REVERSE | DRC_TRIGGER,
				plane2, plane1);
			dp->drcc_next = dptrig;
		    }
		}
	    }
	    else
	    {
		if ((pset = (DBTypesOnSamePlane(i, j) & pmask2)))
	    	{
		    if (TTMaskHasType(&set1, i) && TTMaskHasType(&set2, j))
		    {
			plane1 = LowestMaskBit(pmask);
			plane2 = LowestMaskBit(pset);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &invM, &set2,
				why, distance,
				DRC_FORWARD | DRC_BOTHCORNERS,
				plane1, plane2);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &invM, &set2,
				why, distance,
				DRC_REVERSE | DRC_BOTHCORNERS,
				plane1, plane2);
			dp->drcc_next = dpnew;
		    }
		}
	    }
	}
    }

    if (isExact)
    {
	for (i = 0; i < DBNumTypes; i++)
	{
	    for (j = 0; j < DBNumTypes; j++)
	    {
		if (i == j) continue;	/* Ignore false edges */
		if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
		{
		    if (TTMaskHasType(&setM, i) && TTMaskHasType(&set2, j))
		    {
			plane1 = LowestMaskBit(pset);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &set1, &set2,
					why, distance,
					DRC_FORWARD | DRC_BOTHCORNERS | DRC_OUTSIDE,
					plane1, plane1);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *)mallocMagic(sizeof(DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next, &set1, &set2,
					why, distance,
					DRC_REVERSE | DRC_BOTHCORNERS | DRC_OUTSIDE,
					plane1, plane1);
			dp->drcc_next = dpnew;
		    }
		}
	    }
	}
    }

    if ((!isExact) && strcmp(presence, "absence_illegal")) return distance;

    /* Add an extra rule when presence of the surrounding	*/
    /* layer is required.  Rule is different if planes match.	*/

    if ((pset = pmask & pmask2))
    {
	TTMaskZero(&invM);
	TTMaskSetMask(&invM, &setM);
	TTMaskSetMask(&invM, &set2);
	TTMaskCom(&invM);
	TTMaskZero(&set1);

	for (i = 0; i < DBNumTypes; i++)
	    for (j = 0; j < DBNumTypes; j++)
	    {
		if (i == j) continue;
	        if ((pset = (DBTypesOnSamePlane(i, j) & pmask & pmask2)))
		{
		    plane1 = LowestMaskBit(pset);
		    if (TTMaskHasType(&setM, i) && TTMaskHasType(&invM, j))
		    {
			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
	 		drcAssign(dpnew, distance, dp->drcc_next,
					&set1, &invM, why, distance,
					DRC_FORWARD | DRC_BOTHCORNERS,
					plane1, plane1);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
			drcAssign(dpnew,distance,dp->drcc_next,
					&set1, &invM, why, distance,
					DRC_REVERSE | DRC_BOTHCORNERS,
					plane1, plane1);
			dp->drcc_next = dpnew;
		    }
		}
	    }
    }
    else
    {
	for (i = 0; i < DBNumTypes; i++)
	    for (j = 0; j < DBNumTypes; j++)
	    {
		if (i == j) continue;
	        if ((pset = (DBTypesOnSamePlane(i, j) & pmask)))
		{
		    if (TTMaskHasType(&setM, i) && TTMaskHasType(&invM, j))
		    {
			plane1 = LowestMaskBit(pset);
			plane2 = LowestMaskBit(pmask2);

			/* Find bucket preceding the new one we wish to insert */
			dp = drcFindBucket(i, j, distance);
			dpnew = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
			drcAssign(dpnew, distance, dp->drcc_next,
					&set2, &invM, why, distance,
					DRC_FORWARD | DRC_BOTHCORNERS,
					plane2, plane1);
			dp->drcc_next = dpnew;

			/* find bucket preceding new one we wish to insert */
			dp = drcFindBucket(j, i, distance);
			dpnew = (DRCCookie *) mallocMagic((unsigned)
					(sizeof (DRCCookie)));
			drcAssign(dpnew,distance,dp->drcc_next,
					&set2, &invM, why, distance,
					DRC_REVERSE | DRC_BOTHCORNERS,
					plane2, plane1);
			dp->drcc_next = dpnew;
		    }
		}
	    }
    }

    return distance;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcNoOverlap --
 *
 * Process a no-overlap rule.
 * This is of the form:
 *
 *	no_overlap layers1 layers2
 *
 * e.g,
 *
 *	no_overlap poly m2contact
 *
 * Results:
 *	Returns 0.
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcNoOverlap(argc, argv)
    int argc;
    char *argv[];
{
    char *layers1 = argv[1], *layers2 = argv[2];
    TileTypeBitMask set1, set2;
    TileType i, j;
    int plane;

    /*
     * Grab up two sets of tile types, and make sure that if
     * any type from one set is painted over any type from the
     * other, then an error results.
     */

    DBTechNoisyNameMask(layers1, &set1);
    DBTechNoisyNameMask(layers2, &set2);

    for (i = 0; i < DBNumTypes; i++)
	for (j = 0; j < DBNumTypes; j++)
	    if (TTMaskHasType(&set1, i) && TTMaskHasType(&set2, j))
		for (plane = 0; plane < DBNumPlanes; plane++)
		{
		    DRCCurStyle->DRCPaintTable[plane][j][i] = TT_ERROR_S;
		    DRCCurStyle->DRCPaintTable[plane][i][j] = TT_ERROR_S;
		}

    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcExactOverlap --
 *
 * Process an exact overlap
 * This is of the form:
 *
 *	exact_overlap layers
 *
 * e.g,
 *
 *	exact_overlap pmc,dmc
 *
 * Results:
 *	Returns 0.
 *
 * Side effects:
 *	Updates DRCExactOverlapTypes.
 *
 * ----------------------------------------------------------------------------
 */

int
drcExactOverlap(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    TileTypeBitMask set;

    /*
     * Grab up a bunch of tile types, and remember these: tiles
     * of these types cannot overlap themselves in different cells
     * unless they overlap exactly.
     */

    DBTechNoisyNameMask(layers, &set);
    TTMaskSetMask(&DRCCurStyle->DRCExactOverlapTypes, &set);
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcRectangle --
 *
 * Process a rectangle rule.  This is of the form:
 *
 *	rectangle layers maxwidth [even|odd|any] why
 *
 * The rule checks to make sure that the region is rectangular and that the
 * width and length are even or odd, as specified.  These two criteria ensure
 * that the squares rule of the cifout section can properly produce via
 * holes without misaligning them between cells and without putting the via
 * holes off grid.  The maxwidth is required to make the extent of this rule
 * a finite size, so that we can set the DRChalo to something finite.
 *
 * Results:
 *	maxwidth
 *
 * Side effects:
 *	Updates the DRC technology variables.
 *
 * ----------------------------------------------------------------------------
 */

int
drcRectangle(argc, argv)
    int argc;
    char *argv[];
{
    char *layers = argv[1];
    int why = drcWhyCreate(argv[4]);
    TileTypeBitMask types, nottypes;
    int maxwidth;
    static const char * const drcRectOpt[4] = {"any", "even", "odd", 0};
    int i, j, even, plane;
    PlaneMask pMask, pset, ptest;

    /* parse arguments */
    ptest = DBTechNoisyNameMask(layers, &types);
    pMask = CoincidentPlanes(&types, ptest);

    if (pMask == 0) {
	TechError("Layers in rectangle rule must lie in a single plane.");
	return 0;
    }
    TTMaskCom2(&nottypes, &types);

    if (sscanf(argv[2], "%d", &maxwidth) != 1) {
	TechError("bad maxwidth in rectangle rule");
	return 0;
    }
    even = Lookup(argv[3], drcRectOpt);
    if (even < 0) {
	TechError("bad [even|odd|any] selection in rectangle rule");
	return 0;
    }
    even--;  /* -1: any, 0: even, 1: odd */

    /* Install 2 edge rules: one that checks rectangle-ness, and one that
     * checks size
     */
    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    if (i == j) continue;

	    if ((pset = (DBTypesOnSamePlane(i, j) & pMask)))
	    {
		if (TTMaskHasType(&types, i) && TTMaskHasType(&nottypes, j))
		{
		    DRCCookie *dp, *dpnew;

		    plane = LowestMaskBit(pset);

		    /*
		     * A rule that checks rectangle-ness.
		     *   left:  oktypes, right: other types
		     * This rule needs to be checked in all 4 directions
		     */
		    int distance = 1;

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(i, j, distance);
		    dpnew = (DRCCookie *) mallocMagic((unsigned) (sizeof (DRCCookie)));
		    drcAssign(dpnew, distance, dp->drcc_next,
				&nottypes, &DBAllTypeBits, why, distance,
				DRC_FORWARD, plane, plane);
		    dp->drcc_next = dpnew;

		    /* Find bucket preceding the new one we wish to insert */
		    dp = drcFindBucket(j, i, distance); /* note: j, i not i, j */
		    dpnew = (DRCCookie *) mallocMagic((unsigned) (sizeof (DRCCookie)));
		    drcAssign(dpnew, distance, dp->drcc_next,
		    		&nottypes, &DBAllTypeBits, why, distance,
		    		DRC_REVERSE, plane, plane);
		    dp->drcc_next = dpnew;

		    if (maxwidth > 0) {
			/*
		 	 * A rule that checks size.
		 	 *   left:  other types, right: oktypes
		 	 */
			distance = maxwidth;

			/* note: j, i not i, j */
			for (dp = DRCCurStyle->DRCRulesTbl[j][i];
				dp->drcc_next != (DRCCookie *) NULL &&
				dp->drcc_next->drcc_dist < distance;
				dp = dp->drcc_next); /* null body */

			dpnew = (DRCCookie *)mallocMagic(sizeof (DRCCookie));
			drcAssign(dpnew, distance, dp->drcc_next,
				&types, &DBZeroTypeBits, why, even,
				DRC_RECTSIZE, plane, plane);
			dp->drcc_next = dpnew;
		    }
		}
	    }
	}
    }
    return maxwidth;
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcOption --
 *
 * Process an option declaration
 * This is of the form:
 *
 *	option option_name [...]
 *
 * e.g,
 *
 *	option wide-width-inclusive
 *
 * Results:
 *	Returns 0.
 *
 * Side effects:
 *	Updates DRCFlags.
 *
 * ----------------------------------------------------------------------------
 */

int
drcOption(argc, argv)
    int argc;
    char *argv[];
{
    int i;

    if (DRCCurStyle == NULL) return 0;

    /* Assume space-separated list of options.  Flag an error on any	*/
    /* option name not recognized.					*/

    for (i = 1; i < argc; i++)
    {
	if (!strcmp(argv[i], "wide-width-noninclusive"))
	    DRCCurStyle->DRCFlags |= DRC_FLAGS_WIDEWIDTH_NONINCLUSIVE;
	else
 	{
	    TechError("Unrecognized DRC option \"%s\" (ignored).\n", argv[i]);
	}
    }
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcStepSize --
 *
 * Process a declaration of the step size.
 * This is of the form:
 *
 *	stepsize step_size
 *
 * e.g,
 *
 *	stepsize 1000
 *
 * Results:
 *	Returns 0.
 *
 * Side effects:
 *	Updates DRCStepSize.
 *
 * ----------------------------------------------------------------------------
 */

int
drcStepSize(argc, argv)
    int argc;
    char *argv[];
{
    if (DRCCurStyle == NULL) return 0;

    DRCCurStyle->DRCStepSize = atoi(argv[1]);
    if (DRCCurStyle->DRCStepSize <= 0)
    {
	TechError("Step size must be a positive integer.\n");
	DRCCurStyle->DRCStepSize = 0;
    }
    else if (DRCCurStyle->DRCStepSize < 16)
    {
	TechError("Warning: abnormally small DRC step size (%d)\n",
		DRCCurStyle->DRCStepSize);
    }

    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCTechFinal --
 *
 * Called after all lines of the drc section in the technology file have been
 * read.  Ensures that a valid style is in effect, and then calls
 * drcTechFinalStyle().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See drcTechFinalStyle();
 *
 * ----------------------------------------------------------------------------
 */

void
DRCTechFinal()
{
    DRCStyle *ds;

    /* Create a "default" style if there isn't one */

    if (DRCStyleList == NULL)
    {
	DRCStyleList = (DRCKeep *)mallocMagic(sizeof(DRCKeep));
	DRCStyleList->ds_next = NULL;
	DRCStyleList->ds_name = StrDup((char **)NULL, "default");

	drcTechNewStyle();
	DRCCurStyle->ds_name = DRCStyleList->ds_name;
	DRCCurStyle->ds_status = TECH_LOADED;
    }
    drcTechFinalStyle(DRCCurStyle);
}

/*
 * ----------------------------------------------------------------------------
 * drcScaleDown ---
 *
 *	DRC distances may be specified with a scale factor so that physically
 *	based rules can be recorded, but the rules used will be rounded (up)
 *	to the nearest lambda.  The fractional part of the true distance in
 *	lambda is saved, so that the original value can be recovered when
 *	the magic grid is rescaled.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Scales all the DRC distances by dividing by the DRC scale factor.
 * ----------------------------------------------------------------------------
 */

void
drcScaleDown(style, scalefactor)
    DRCStyle *style;
    int scalefactor;
{
    TileType i, j;
    DRCCookie  *dp;
    int dist;

    if (scalefactor > 1)
    {
	for (i = 0; i < TT_MAXTYPES; i++)
	    for (j = 0; j < TT_MAXTYPES; j++)
		for (dp = style->DRCRulesTbl[i][j]; dp != NULL; dp = dp->drcc_next)
		{
		    if  (dp->drcc_dist > 0)
		    {
			dist = dp->drcc_dist;
			dp->drcc_dist /= scalefactor;
			if ((dp->drcc_mod = (unsigned char)(dist % scalefactor)) != 0)
			    if (!(dp->drcc_flags & DRC_MAXWIDTH))
				dp->drcc_dist++;
		    }
		    if  (dp->drcc_cdist > 0)
		    {
			int cmod;
			int locscale = scalefactor;
			if (dp->drcc_flags & DRC_AREA)
			    locscale *= scalefactor;

			dist = dp->drcc_cdist;
			dp->drcc_cdist /= locscale;

			/* Save the amount by which the distance needs to be
			 * corrected when multiplied back to the original
			 * scale.  For area, the modulus will always be
			 * a multiple of the scalefactor, so it can be
			 * divided by the scalefactor so it still fits in an
			 * unsigned char.
			 */

			if ((cmod = (dist % locscale)) != 0)
			{
			    dp->drcc_cdist++;
			    if (dp->drcc_flags & DRC_AREA)
				dp->drcc_cmod = (unsigned char)(cmod / scalefactor);
			    else
				dp->drcc_cmod = (unsigned char)cmod;
			}
		    }
		}
    }
}

/*
 * ----------------------------------------------------------------------------
 * drcScaleUp ---
 *
 *	Recovers the original (pre-scaled) values for drcc_dist and
 *	drcc_cdist in the DRC cookies.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Scales all the DRC distances by multiplying by the DRC scale factor.
 * ----------------------------------------------------------------------------
 */

void
drcScaleUp(style, scalefactor)
    DRCStyle *style;
    int scalefactor;
{
    TileType i, j;
    DRCCookie  *dp;
    int dist;

    if (style == NULL) return;

    if (scalefactor > 1)
    {
	for (i = 0; i < TT_MAXTYPES; i++)
	    for (j = 0; j < TT_MAXTYPES; j++)
		for (dp = style->DRCRulesTbl[i][j]; dp != NULL; dp = dp->drcc_next)
		{
		    if  (dp->drcc_dist > 0)
		    {
			dist = dp->drcc_dist;
			if (dp->drcc_mod != 0)
			    if (!(dp->drcc_flags & DRC_MAXWIDTH))
				dp->drcc_dist--;
			dp->drcc_dist *= scalefactor;
			dp->drcc_dist += (short)dp->drcc_mod;
			dp->drcc_mod = 0;
		    }
		    if  (dp->drcc_cdist > 0)
		    {
			dist = dp->drcc_cdist;
			if (dp->drcc_cmod != 0)
			    dp->drcc_cdist--;
			dp->drcc_cdist *= scalefactor;
			if (dp->drcc_flags & DRC_AREA)
			{
			    dp->drcc_cdist *= scalefactor;
			    /* See note above on how cmod is divided by the
			     * scalefactor for area values.
			     */
			    dp->drcc_cdist += (short)(dp->drcc_cmod * scalefactor);
			}
			else
			    dp->drcc_cdist += (short)dp->drcc_cmod;
			dp->drcc_cmod = 0;
		    }
		}
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * drcTechFinalStyle --
 *
 * Called after all lines of the drc section in the technology file have been
 * read.  The preliminary DRC Rules Table is pruned by removing rules covered
 * by other (longer distance) rules, and by removing the dummy rule at the
 * front of each list.  Where edges are completely illegal, the rule list is
 * pruned to a single rule.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May remove DRCCookies from the linked lists of the DRCRulesTbl.
 *
 * ----------------------------------------------------------------------------
 */

void
drcTechFinalStyle(style)
   DRCStyle *style;
{
    TileTypeBitMask tmpMask, nextMask;
    DRCCookie  *dummy, *dp, *next, *dptrig;
    DRCCookie **dpp, **dp2back;
    TileType i, j;

    /* Done with DRCWhyErrorTable */
    HashKill(&DRCWhyErrorTable);

    /* If the scale factor is not 1, then divide all distances by	*/
    /* the scale factor, take the ceiling, and save the (negative)	*/
    /* remainder.							*/

    drcScaleUp(style, style->DRCScaleFactorD);
    drcScaleDown(style, style->DRCScaleFactorN);

    /* Scale DRCTechHalo to match */
    DRCTechHalo *= style->DRCScaleFactorD;
    DRCTechHalo /= style->DRCScaleFactorN;

    /* Set maximum halo */
    style->DRCTechHalo = DRCTechHalo;

    /* A reasonable chunk size for design-rule checking is about
     * 16 times the maximum design-rule interaction distance.  This
     * results in a halo overhead of about 27%.  If there's no DRC
     * information at all (TechHalo is zero), just pick any size.
     * (Update 1/13/09:  "any size" needs a bit of modification,
     * because 64 will be way too small for a layout with a small
     * scalefactor.  Assuming that the CIF output style is valid,
     * use its scalefactor to adjust the step size).
     */
    if (style->DRCStepSize == 0)
    {
	if (style->DRCTechHalo == 0)
	{
	    if (CIFCurStyle != NULL)
		style->DRCStepSize = 6400 / CIFCurStyle->cs_scaleFactor;
	    else
		style->DRCStepSize = 64;
	}
	else
	    style->DRCStepSize = 16 * style->DRCTechHalo;
    }
    DRCStepSize = style->DRCStepSize;

    /* Remove dummy buckets */
    for (i = 0; i < TT_MAXTYPES; i++)
    {
	for (j = 0; j < TT_MAXTYPES; j++)
	{
	    dpp = &(style->DRCRulesTbl [i][j]);
	    dummy = *dpp;
	    *dpp = dummy->drcc_next;
	    freeMagic((char *) dummy);
	}
    }
    drcCifFinal();

    if (!DRCRuleOptimization) return;

    /* Check for edges that are completely illegal.  Where this is the
     * case, eliminate all of the edge's rules except one.
     */

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    DRCCookie *keep = NULL, *dptest, *dptemp, *dpnew;

	    for (dp = style->DRCRulesTbl[i][j]; dp != NULL; dp = dp->drcc_next)
	    {
		if (dp->drcc_flags & (DRC_NONSTANDARD | DRC_OUTSIDE)) continue;
		if (dp->drcc_flags & DRC_REVERSE)
		{
		    if ((i == TT_SPACE) || TTMaskHasType(&dp->drcc_mask, i)) continue;
		}
		else
		{
		    if ((j == TT_SPACE) || TTMaskHasType(&dp->drcc_mask, j)) continue;
		}

		/* Rules where okTypes are in a different plane don't count,	*/
		/* unless i or j also appear in the checked plane.		*/

		if (dp->drcc_plane != dp->drcc_edgeplane)
		{
		    if (dp->drcc_flags & DRC_REVERSE)
		    {
			if ((i == TT_SPACE) || !DBTypeOnPlane(i, dp->drcc_plane))
			    continue;
		    }
		    else
		    {
			if ((j == TT_SPACE) || !DBTypeOnPlane(j, dp->drcc_plane))
			    continue;
		    }
		}

		// if (DBIsContact(i) || DBIsContact(j) || i == TT_SPACE ||
		//	j == TT_SPACE) continue;

		dpnew = NULL;
		keep = dp;

		/* This edge is illegal.  Throw away all rules except the one
		 * needed that is always violated.
		 */

		dptest = style->DRCRulesTbl[i][j];
		while (dptest != NULL)
		{
		    dptemp = dptest->drcc_next;
		    if ((dptest == keep) || (dptest->drcc_edgeplane !=
				keep->drcc_edgeplane))
		    {
			dptest->drcc_next = NULL;
			if (dpnew == NULL)
			    style->DRCRulesTbl[i][j] = dptest;
			else
			    dpnew->drcc_next = dptest;
			dpnew = dptest;

			/* "keep" can't be a trigger rule! */
			if (dptest == keep)
			    keep->drcc_flags &= ~DRC_TRIGGER;
		    }
		    else
		    {
			freeMagic((char *)dptest);
			drcRulesOptimized++;
		    }
		    dptest = dptemp;
		}
	    }

	    /* TxPrintf("Edge %s-%s is illegal.\n", DBTypeShortName(i),
		DBTypeShortName(j));
	    */
	}
    }

    /*
     * Remove any rule A "covered" by another rule B, i.e.,
     *		B's distance >= A's distance,
     *		B's corner distance >= A's corner distance,
     *		B's RHS type mask is a subset of A's RHS type mask, and
     *		B's corner mask == A's corner mask
     *		B's check plane == A's check plane
     *		either both A and B or neither is a REVERSE direction rule
     *		if A is BOTHCORNERS then B must be, too
     */

    for (i = 0; i < DBNumTypes; i++)
    {
	for (j = 0; j < DBNumTypes; j++)
	{
	    for (dp = style->DRCRulesTbl[i][j]; dp != NULL; dp = dp->drcc_next)
	    {
		/* Don't optimize on trigger rules; optimize on the	*/
		/* rule that gets triggered.				*/
		if (dp->drcc_flags & DRC_TRIGGER)
		{
		    dptrig = dp;
		    dp = dp->drcc_next;
		}
		else
		    dptrig = NULL;

		/*
		 * Check following buckets to see if any is a superset.
		 */
		if (dp->drcc_flags & DRC_NONSTANDARD) continue;
		for (next = dp->drcc_next; next != NULL;
			next = next->drcc_next)
		{
		    if (next->drcc_flags & DRC_TRIGGER)
		    {
			/* A triggered rule cannot be considered */
			/* a superset of a non-triggered rule or */
			/* a rule with a different trigger, so   */
			/* we skip all triggered rules and their */
			/* triggering rule.			 */
			next = next->drcc_next;
			continue;
		    }
		    tmpMask = nextMask = next->drcc_mask;
		    TTMaskAndMask(&tmpMask, &dp->drcc_mask);
		    if (!TTMaskEqual(&tmpMask, &nextMask)) continue;
		    if (!TTMaskEqual(&dp->drcc_corner, &next->drcc_corner))
			continue;
		    if (dp->drcc_dist > next->drcc_dist) continue;
		    if (dp->drcc_cdist > next->drcc_cdist) continue;
		    if (dp->drcc_plane != next->drcc_plane) continue;
		    if (dp->drcc_flags & DRC_REVERSE)
		    {
			if (!(next->drcc_flags & DRC_REVERSE)) continue;
		    }
		    else if (next->drcc_flags & DRC_REVERSE) continue;
		    if ((next->drcc_flags & DRC_BOTHCORNERS)
			    && (dp->drcc_flags & DRC_BOTHCORNERS) == 0)
			continue;
		    if (next->drcc_flags & DRC_NONSTANDARD) continue;
		    if (dp->drcc_dist == next->drcc_dist)
		    {
			if ((next->drcc_flags & DRC_OUTSIDE) &&
				!(dp->drcc_flags & DRC_OUTSIDE)) continue;
			if (!(next->drcc_flags & DRC_OUTSIDE) &&
				(dp->drcc_flags & DRC_OUTSIDE)) continue;
		    }
		    if (dp->drcc_flags & DRC_SPLITTILE)
		    {
			if (!(next->drcc_flags & DRC_SPLITTILE)) continue;
		    }
		    else if (next->drcc_flags & DRC_SPLITTILE) continue;
		    break;
		}

		if (next == NULL) continue;

		/* "dp" is a subset of "next".  Eliminate it. */

		/* For triggered rules, eliminate both the rule */
		/* and the trigger.				*/
		if (dptrig != NULL) dp = dptrig;

		/* TxPrintf("For edge %s-%s, \"%s\" covers \"%s\"\n",
		    DBTypeShortName(i), DBTypeShortName(j),
		    DRCCurStyle->DRCWhyList[next->drcc_tag],
		    DRCCurStyle->DRCWhyList[dp->drcc_tag]);
		*/
		dp2back = &(style->DRCRulesTbl[i][j]);
		while (*dp2back != dp)
		    dp2back = &(*dp2back)->drcc_next;

		/* Trigger rules */
		if (dptrig != NULL)
		{
		    dptrig = dp->drcc_next;
		    freeMagic((char *)dp->drcc_next);
		    *dp2back = dp->drcc_next->drcc_next;

		    /* Replace this entry so on the next cycle	*/
		    /* dp will be the next rule.  This works	*/
		    /* even though dp is free'd (below), due to	*/
		    /* the one-delayed free mechanism.		*/
		    dp->drcc_next = *dp2back;
		}
		else
		    *dp2back = dp->drcc_next;

		freeMagic((char *) dp);
		drcRulesOptimized += 1;
	    }
	}
    }
}


/*
 * ----------------------------------------------------------------------------
 *
 * DRCTechRuleStats --
 *
 * 	Print out some statistics about the design rule database.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A bunch of stuff gets printed on the terminal.
 *
 * ----------------------------------------------------------------------------
 */

#define MAXBIN 10

void
DRCTechRuleStats()
{
    int counts[MAXBIN+1];
    int edgeRules, overflow;
    int i, j;
    DRCCookie *dp;

    /* Count up the total number of edge rules, and histogram them
     * by the number of rules per edge.
     */

    edgeRules = 0;
    overflow = 0;
    for (i=0; i<=MAXBIN; i++) counts[i] = 0;

    for (i=0; i<DBNumTypes; i++)
	for (j=0; j<DBNumTypes; j++)
	{
	    int thisCount = 0;
	    for (dp = DRCCurStyle->DRCRulesTbl[i][j]; dp != NULL; dp = dp->drcc_next)
		thisCount++;
	    edgeRules += thisCount;
	    if (!DBTypesOnSamePlane(i, j)) continue;
	    if (thisCount <= MAXBIN) counts[thisCount] += 1;
	    else overflow += 1;
	}

    /* Print out the results. */

    TxPrintf("Total number of rules specifed in tech file: %d\n",
	drcRulesSpecified);
    TxPrintf("Edge rules optimized away: %d\n", drcRulesOptimized);
    TxPrintf("Edge rules left in database: %d\n", edgeRules);
    TxPrintf("Histogram of # edges vs. rules per edge:\n");
    for (i=0; i<=MAXBIN; i++)
    {
	TxPrintf("  %2d rules/edge: %d.\n", i, counts[i]);
    }
    TxPrintf(" >%2d rules/edge: %d.\n", MAXBIN, overflow);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DRCTechScale --
 *
 * 	Multiply all DRC rule widths and spacings by a factor of scaled/scalen.
 *	(Don't need to use DBScaleValue() because all values must be positive
 *	and cannot be (M)INFINITY.)
 *
 * ----------------------------------------------------------------------------
 */

void
DRCTechScale(scalen, scaled)
    int scalen, scaled;
{
    DRCCookie  *dp;
    TileType i, j;
    int scalegcf;

    if (DRCCurStyle == NULL) return;
    else if (scalen == scaled) return;

    /* Revert DRC rules to original (unscaled) values */
    drcScaleUp(DRCCurStyle, DRCCurStyle->DRCScaleFactorN);
    drcScaleDown(DRCCurStyle, DRCCurStyle->DRCScaleFactorD);

    /* Do the same for the plow rules */
    DRCPlowScale(DRCCurStyle->DRCScaleFactorN, DRCCurStyle->DRCScaleFactorD, FALSE);

    DRCCurStyle->DRCScaleFactorD *= scaled;
    DRCCurStyle->DRCScaleFactorN *= scalen;

    /* Reduce scalefactor ratio by greatest common factor */
    scalegcf = FindGCF(DRCCurStyle->DRCScaleFactorD, DRCCurStyle->DRCScaleFactorN);
    DRCCurStyle->DRCScaleFactorD /= scalegcf;
    DRCCurStyle->DRCScaleFactorN /= scalegcf;

    /* Rescale all rules to the new scalefactor */
    drcScaleUp(DRCCurStyle, DRCCurStyle->DRCScaleFactorD);
    drcScaleDown(DRCCurStyle, DRCCurStyle->DRCScaleFactorN);

    /* Do the same for the plow rules */
    DRCPlowScale(DRCCurStyle->DRCScaleFactorD, DRCCurStyle->DRCScaleFactorN, TRUE);

    DRCTechHalo *= scaled;
    DRCTechHalo /= scalen;

    DRCStepSize *= scaled;
    DRCStepSize /= scalen;

    DRCCurStyle->DRCTechHalo *= scaled;
    DRCCurStyle->DRCTechHalo /= scalen;

    DRCCurStyle->DRCStepSize *= scaled;
    DRCCurStyle->DRCStepSize /= scalen;
}

/* The following routines are used by the "tech" command (and in other places,
 * such as the LEF file reader) to query the DRC database.
 */

/*
 *-----------------------------------------------------------------------------
 *  DRCGetDefaultLayerWidth ---
 *
 *	Determine a default layer width from the DRC width rules
 *	of a layer.  Continue processing until we have processed all
 *	rules, since rules are ordered from shortest to longest distance,
 *	and the maximum distance rule will mask any rules with a shorter
 *	distance.
 *
 * Results:
 *	The minimum width of the magic layer, in magic internal units
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

int
DRCGetDefaultLayerWidth(ttype)
    TileType ttype;
{
    int routeWidth = 0;
    DRCCookie *cptr;
    TileTypeBitMask *set;

    if (ttype < 0)
    {
	TxError("Error:  Attempt to get default width of invalid layer!\n");
	return 0;
    }

    for (cptr = DRCCurStyle->DRCRulesTbl[TT_SPACE][ttype]; cptr != (DRCCookie *) NULL;
	cptr = cptr->drcc_next)
    {
	/* Skip triggered and triggering rules */
	if (cptr->drcc_flags & DRC_TRIGGER)
	{
	    cptr = cptr->drcc_next;
	    continue;
	}

	/* Skip area rule */
	if (cptr->drcc_flags & DRC_AREA) continue;

	/* FORWARD rules only, and no MAXWIDTH or SPLITTILE */
	if ((cptr->drcc_flags & (DRC_REVERSE | DRC_MAXWIDTH | DRC_SPLITTILE)) == 0)
	{
	    set = &cptr->drcc_mask;
	    if (TTMaskHasType(set, ttype) && TTMaskEqual(set, &cptr->drcc_corner))
		if ((cptr->drcc_plane == DBPlane(ttype)) &&
			(cptr->drcc_dist == cptr->drcc_cdist))
		{
		    routeWidth = cptr->drcc_dist;
		    /* Diagnostic */
		    /*
		    TxPrintf("DRC: Layer %s has default width %d\n",
		    DBTypeLongNameTbl[ttype], routeWidth);
		    */
		}
	}
    }
    return routeWidth;
}

/*
 *-----------------------------------------------------------------------------
 *  DRCGetDefaultLayerSpacing ---
 *
 *	Determine a default layer-to-layer spacing from the DRC width
 *	rules of a layer.  Continue processing all rules, since rules
 *	are ordered from shortest to longest distance, and the largest
 *	distance matching the criteria sets the rule.
 *
 * Results:
 *	The minimum spacing between the specified magic layer types,
 *	in magic internal units
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

int
DRCGetDefaultLayerSpacing(ttype1, ttype2)
    TileType ttype1, ttype2;
{
    int routeSpacing = 0;
    DRCCookie *cptr;
    TileTypeBitMask *set;

    for (cptr = DRCCurStyle->DRCRulesTbl[ttype1][TT_SPACE]; cptr != (DRCCookie *) NULL;
	cptr = cptr->drcc_next)
    {
	if (cptr->drcc_flags & DRC_TRIGGER) {		/* Skip widespacing rules */
	    cptr = cptr->drcc_next;
	    continue;
	}

	/* Skip area rule */
	if (cptr->drcc_flags & DRC_AREA) continue;

	if ((cptr->drcc_flags & DRC_REVERSE) == 0)	/* FORWARD only */
	{
	    set = &cptr->drcc_mask;
	    if (!TTMaskHasType(set, ttype2))
	        if (PlaneMaskHasPlane(DBTypePlaneMaskTbl[ttype2], cptr->drcc_plane) &&
			(cptr->drcc_dist == cptr->drcc_cdist))
		{
		    routeSpacing = cptr->drcc_dist;
		    /* Diagnostic */
		    /*
		    TxPrintf("DRC: Layer %s has default spacing %d to layer %s\n",
			DBTypeLongNameTbl[ttype1], routeSpacing,
			DBTypeLongNameTbl[ttype2]);
		    */
		}
	}
    }
    return routeSpacing;
}

/*
 *-----------------------------------------------------------------------------
 *  DRCGetDefaultLayerSurround ---
 *
 *	Determine the default minimum required surround amount
 *	of layer type 2 around layer type 1.
 *	Continue processing all rules, since rules are ordered from
 *	shortest to longest distance, and the largest value of the
 *	surround material sets the minimum required width.
 *
 * Results:
 *	The minimum spacing between the specified magic layer types,
 *	in magic internal units
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

int
DRCGetDefaultLayerSurround(ttype1, ttype2)
    TileType ttype1, ttype2;
{
    int layerSurround = 0;
    DRCCookie *cptr;
    TileTypeBitMask *set, *cset;

    for (cptr = DRCCurStyle->DRCRulesTbl[ttype1][TT_SPACE]; cptr != (DRCCookie *) NULL;
	cptr = cptr->drcc_next)
    {
	if ((cptr->drcc_flags & DRC_REVERSE) == 0)	/* FORWARD only */
	{
	    set = &cptr->drcc_mask;
	    if (!TTMaskHasType(set, TT_SPACE))
	        if (PlaneMaskHasPlane(DBTypePlaneMaskTbl[ttype2], cptr->drcc_plane) &&
			(cptr->drcc_dist == cptr->drcc_cdist))
		{
		    layerSurround = cptr->drcc_dist;
		    /* Diagnostic */
		    /*
		    TxPrintf("DRC: Layer %s has default surround %d over layer %s\n",
			DBTypeLongNameTbl[ttype2], layerSurround,
			DBTypeLongNameTbl[ttype1]);
		    */
		}
	}
    }
    if (layerSurround > 0) return layerSurround;

    for (cptr = DRCCurStyle->DRCRulesTbl[TT_SPACE][ttype1]; cptr != (DRCCookie *) NULL;
	cptr = cptr->drcc_next)
    {
	if ((cptr->drcc_flags & DRC_REVERSE) == 0)	/* FORWARD only */
	{
	    set = &cptr->drcc_mask;
	    cset = &cptr->drcc_corner;
	    if (TTMaskHasType(set, TT_SPACE) && !TTMaskHasType(set, ttype1))
	        if ((TTMaskHasType(cset, ttype2)) &&
			(cptr->drcc_flags & DRC_BOTHCORNERS) &&
			(cptr->drcc_edgeplane == cptr->drcc_plane) &&
			(cptr->drcc_dist == cptr->drcc_cdist))
		{
		    layerSurround = cptr->drcc_dist;
		    /* Diagnostic */
		    /*
		    TxPrintf("DRC: Layer %s has default surround %d over layer %s\n",
			DBTypeLongNameTbl[ttype2], layerSurround,
			DBTypeLongNameTbl[ttype1]);
		    */
		}
	}
    }
    return layerSurround;
}

/*
 *-----------------------------------------------------------------------------
 *  DRCGetDirectionalLayerSurround ---
 *
 *	Determine the minimum required surround amount of layer type 2
 *	around layer type 1 for a directional surround rule (rule
 *	applies for two opposing sides of layer type 1).
 *
 * Results:
 *	The minimum spacing between the specified magic layer types,
 *	in magic internal units
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

int
DRCGetDirectionalLayerSurround(ttype1, ttype2)
    TileType ttype1, ttype2;
{
    int layerSurround = 0;
    DRCCookie *cptr, *cnext;
    TileTypeBitMask *set, *cset;

    for (cptr = DRCCurStyle->DRCRulesTbl[ttype1][TT_SPACE]; cptr != (DRCCookie *) NULL;
	cptr = cptr->drcc_next)
    {
	if (cptr->drcc_flags & DRC_TRIGGER)
	{
	    set = &cptr->drcc_mask;
	    if (!TTMaskHasType(set, TT_SPACE) && TTMaskHasType(set, ttype2))
	        if ((cptr->drcc_plane == cptr->drcc_edgeplane) &&
			(cptr->drcc_cdist == 0))
		{
		    layerSurround = cptr->drcc_dist;
		    /* Diagnostic */
		    /*
		    TxPrintf("DRC: Layer %s has default surround %d over layer %s\n",
			DBTypeLongNameTbl[ttype2], layerSurround,
			DBTypeLongNameTbl[ttype1]);
		    */
		}
	    cnext = cptr->drcc_next;
	}
    }
    return layerSurround;
}

/*
 *-----------------------------------------------------------------------------
 *  DRCGetDefaultLayerWideSpacing ---
 *
 *	Determine a default layer-to-self wide-layer spacing rule from
 *	the DRC width rules of a layer.
 *
 * Results:
 *	The minimum spacing between the specified magic layer type and
 *	itself, where one of the shapes has width greater than "twidth".
 *	The result value is in magic internal units.
 *
 *	If "twidth" is zero, then return the maximum spacing rule distance.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

int
DRCGetDefaultWideLayerSpacing(ttype, twidth)
    TileType ttype;
    int twidth;
{
    int routeSpacing = 0;
    DRCCookie *cptr;
    TileTypeBitMask *set;
    bool widerule = FALSE;

    for (cptr = DRCCurStyle->DRCRulesTbl[ttype][TT_SPACE]; cptr != (DRCCookie *) NULL;
	cptr = cptr->drcc_next)
    {
	if (cptr->drcc_flags & DRC_TRIGGER)		/* Widespacing rule */
	{
	    widerule = TRUE;
	    if (twidth > 0 && cptr->drcc_dist > twidth)   /* Check against rule width */
		return routeSpacing;
	}
	if (widerule && ((cptr->drcc_flags & DRC_REVERSE) == 0))    /* FORWARD only */
	{
	    set = &cptr->drcc_mask;
	    if (!TTMaskHasType(set, ttype))
	        if (PlaneMaskHasPlane(DBTypePlaneMaskTbl[ttype], cptr->drcc_plane) &&
			(cptr->drcc_dist == cptr->drcc_cdist))
		{
		    routeSpacing = cptr->drcc_dist;
		    /* Diagnostic */
		    /*
		    TxPrintf("DRC: Layer %s has wide spacing %d to layer %s width %d\n",
			DBTypeLongNameTbl[ttype1], routeSpacing,
			DBTypeLongNameTbl[ttype2], twidth);
		    */
		}
	}
	if (!(cptr->drcc_flags & DRC_TRIGGER)) widerule = FALSE;
    }
    return routeSpacing;
}

